Repository: woocommerce/woocommerce-android
Pull request #13919: [Jetpack Performance Experiment] tracking + some fixes
Author: login: hichamboushaba
Labels: name: type: task
description: An internally driven task.
name: category: tracks
description: Related to analytics, including Tracks Events.
name: category: performance
description: Related to performance such as slow loading.
Created: 2025-04-14T16:06:49Z | Merged: 2025-04-17T22:41:26Z

<!-- Remember about a good descriptive title. -->

Closes: WOOMOB-299
<!-- Id number of the GitHub issue this PR addresses. -->

<!-- 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🛠 Need an AI review?  
Add a comment: `@coderabbitai review`
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-->

### Description
This PR adds tracking for the Jetpack experiment, to achieve what we want, I added a new property to `WPApiResponse` to pass information about the type of the network that was used to make the API call. And when it's missing, we'll consider that the user is not part of the experiment, making collecting the data easier after the experiment.

### Steps to reproduce
Check the testing plan here pe5sF9-4ds-p2 

### Testing information
Confirm the test cases work as expected.

### The tests that have been performed
^

- [x] I have considered if this change warrants release notes and have added them to `RELEASE-NOTES.txt` if necessary. Use the "[Internal]" label for non-user-facing changes.

## Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

- [ ] The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
- [ ] Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
- [ ] Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on big (tablet) and small (phone) in case of UI changes, and no regressions are added.

<!-- Pull request guidelines: https://github.com/woocommerce/woocommerce-android/blob/develop/docs/pull-request-guidelines.md -->


Diff:
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt
index a2a9e711e97b..a4eeb4ee4479 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt
@@ -212,6 +212,7 @@ enum class AnalyticsEvent(override val siteless: Boolean = false) : IAnalyticsEv
     ORDERS_LIST_AUTOMATIC_TIMEOUT_RETRY,
     ORDERS_LIST_TOP_BANNER_TROUBLESHOOT_TAPPED,
     TEST_ORDER_START_TAPPED,
+    ORDERS_LIST_APP_PASSWORDS_FAILURE,
 
     FILTER_ORDERS_BY_STATUS_DIALOG_OPTION_SELECTED,
     ORDER_FILTER_LIST_CLEAR_MENU_BUTTON_TAPPED,
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
index bcf232382647..002affca998b 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
@@ -79,6 +79,7 @@ import org.wordpress.android.fluxc.Dispatcher
 import org.wordpress.android.fluxc.model.WCOrderListDescriptor
 import org.wordpress.android.fluxc.model.WCOrderStatusModel
 import org.wordpress.android.fluxc.model.list.PagedListWrapper
+import org.wordpress.android.fluxc.network.rest.wpapi.WPAPINetworkingMode
 import org.wordpress.android.fluxc.network.rest.wpcom.wc.order.CoreOrderStatus
 import org.wordpress.android.fluxc.store.ListStore
 import org.wordpress.android.fluxc.store.ListStore.ListErrorType.PARSE_ERROR
@@ -597,13 +598,29 @@ class OrderListViewModel @Inject constructor(
     @SuppressWarnings("unused")
     @Subscribe(threadMode = MAIN)
     fun onOrderSummariesFetched(event: OnOrderSummariesFetched) {
+        fun WPAPINetworkingMode.toTrackingValue(): String {
+            return when (this) {
+                is WPAPINetworkingMode.ApplicationPasswords -> "app_passwords"
+                is WPAPINetworkingMode.ApplicationPasswordsWithJetpack -> "app_passwords_with_jetpack"
+                is WPAPINetworkingMode.JetpackTunnel -> "jetpack_tunnel"
+            }
+        }
+
         // Only track if this is not from a search query
         if (!event.listDescriptor.searchQuery.isNullOrEmpty()) {
             return
         }
 
         if (event.isError) {
-            AnalyticsTracker.track(AnalyticsEvent.ORDER_LIST_LOAD_ERROR)
+            AnalyticsTracker.track(
+                AnalyticsEvent.ORDER_LIST_LOAD_ERROR,
+                properties = mapOf(
+                    "request_type" to event.networkingMode?.toTrackingValue()
+                ).filterNotNull(),
+                errorType = event.error.type.name,
+                errorContext = this::class.simpleName,
+                errorDescription = event.error.message,
+            )
         } else {
             launch {
                 val totalDurationInSeconds = event.duration.toDouble() / 1_000
@@ -614,9 +631,23 @@ class OrderListViewModel @Inject constructor(
                     mapOf(
                         AnalyticsTracker.KEY_TOTAL_DURATION to totalDurationInSeconds,
                         AnalyticsTracker.KEY_STATUS to event.listDescriptor.statusFilter,
-                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders
+                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders,
+                        "request_type" to event.networkingMode?.toTrackingValue()
                     )
                 )
+
+                if (event.networkingMode is WPAPINetworkingMode.JetpackTunnel &&
+                    (event.networkingMode as WPAPINetworkingMode.JetpackTunnel).isFallback
+                ) {
+                    val error = (event.networkingMode as WPAPINetworkingMode.JetpackTunnel).applicationPasswordsError
+                    AnalyticsTracker.track(
+                        AnalyticsEvent.ORDERS_LIST_APP_PASSWORDS_FAILURE,
+                        properties = mapOf(
+                            "network_error_code" to error?.volleyError?.networkResponse?.statusCode,
+                            "error_api_code" to error?.errorCode,
+                        )
+                    )
+                }
             }
         }
     }
diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetwork.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetwork.kt
index 1fa6d994a962..2e4d2e955f6e 100644
--- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetwork.kt
+++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetwork.kt
@@ -1,12 +1,15 @@
 package org.wordpress.android.fluxc.network.rest.wpcom.wc
 
-import org.wordpress.android.fluxc.Dispatcher
-import org.wordpress.android.fluxc.generated.SiteActionBuilder
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
 import org.wordpress.android.fluxc.model.SiteModel
 import org.wordpress.android.fluxc.network.rest.wpapi.WPAPINetwork
+import org.wordpress.android.fluxc.network.rest.wpapi.WPAPINetworkingMode
 import org.wordpress.android.fluxc.network.rest.wpapi.WPAPIResponse
 import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsNetwork
+import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsStore
 import org.wordpress.android.fluxc.network.rest.wpcom.JetpackTunnelWPAPINetwork
+import org.wordpress.android.fluxc.persistence.SiteSqlUtils
 import org.wordpress.android.util.AppLog
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -22,7 +25,8 @@ import javax.inject.Singleton
 class WooExperimentalNetwork @Inject constructor(
     private val applicationPasswordsNetwork: ApplicationPasswordsNetwork,
     private val jetpackTunnelWPAPINetwork: JetpackTunnelWPAPINetwork,
-    private val dispatcher: Dispatcher
+    private val applicationPasswordsStore: ApplicationPasswordsStore,
+    private val siteSqlUtils: SiteSqlUtils
 ) : WPAPINetwork {
     override suspend fun <T : Any> executeGetGsonRequest(
         site: SiteModel,
@@ -96,7 +100,9 @@ class WooExperimentalNetwork @Inject constructor(
     ): WPAPIResponse<T> {
         return when (site.origin) {
             SiteModel.ORIGIN_WPAPI, SiteModel.ORIGIN_XMLRPC -> {
-                applicationPasswordsNetwork.request()
+                applicationPasswordsNetwork.request().copyWith(
+                    networkingMode = WPAPINetworkingMode.ApplicationPasswords
+                )
             }
 
             SiteModel.ORIGIN_WPCOM_REST -> {
@@ -120,16 +126,25 @@ class WooExperimentalNetwork @Inject constructor(
                 AppLog.T.API,
                 "Application Passwords not supported for site: ${site.url}, use Jetpack Tunnel"
             )
-            return jetpackTunnelWPAPINetwork.request()
+            return jetpackTunnelWPAPINetwork.request().copyWith(
+                networkingMode = WPAPINetworkingMode.JetpackTunnel()
+            )
         }
 
+        val hasAppPassword = applicationPasswordsStore.hasCredentials(site)
+
         return when (val appPasswordsResponse = applicationPasswordsNetwork.request()) {
             is WPAPIResponse.Success<*> -> {
                 AppLog.v(
                     AppLog.T.API,
                     "Request successful for site: ${site.url}, using Application Passwords"
                 )
-                appPasswordsResponse
+                (appPasswordsResponse as WPAPIResponse<T>).copyWith(
+                    // When creating a new Application Password, we don't want to track this request, as its duration
+                    // is not relevant to our experiment.
+                    // So we track only requests where we already have a password saved.
+                    networkingMode = if (hasAppPassword) WPAPINetworkingMode.ApplicationPasswordsWithJetpack else null
+                )
             }
 
             is WPAPIResponse.Error<*> -> {
@@ -140,11 +155,27 @@ class WooExperimentalNetwork @Inject constructor(
                 if (appPasswordsResponse.error.errorCode ==
                     ApplicationPasswordsNetwork.APPLICATION_PASSWORDS_NOT_SUPPORT_ERROR_CODE
                 ) {
-                    site.applicationPasswordsAuthorizeUrl = null
-                    dispatcher.dispatch(SiteActionBuilder.newUpdateSiteAction(site))
+                    withContext(Dispatchers.IO) {
+                        site.applicationPasswordsAuthorizeUrl = null
+                        siteSqlUtils.insertOrUpdateSite(site)
+                    }
                 }
-                jetpackTunnelWPAPINetwork.request()
+                jetpackTunnelWPAPINetwork.request().copyWith(
+                    networkingMode = WPAPINetworkingMode.JetpackTunnel(
+                        isFallback = true,
+                        applicationPasswordsError = appPasswordsResponse.error
+                    )
+                )
             }
         }
     }
+
+    private fun <T : Any> WPAPIResponse<T>.copyWith(
+        networkingMode: WPAPINetworkingMode?
+    ): WPAPIResponse<T> {
+        return when (this) {
+            is WPAPIResponse.Success -> this.copy(networkingMode = networkingMode)
+            is WPAPIResponse.Error -> this.copy(networkingMode = networkingMode)
+        }
+    }
 }
diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/order/OrderRestClient.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/order/OrderRestClient.kt
index 4734b8450c84..d9cb5a0fee7e 100644
--- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/order/OrderRestClient.kt
+++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/order/OrderRestClient.kt
@@ -280,7 +280,8 @@ class OrderRestClient @Inject constructor(
                         orderSummaries = orderSummaries,
                         loadedMore = offset > 0,
                         canLoadMore = canLoadMore,
-                        requestDurationMs = System.currentTimeMillis() - startTime
+                        requestDurationMs = System.currentTimeMillis() - startTime,
+                        networkingMode = response.networkingMode
                     )
                     dispatcher.dispatch(WCOrderActionBuilder.newFetchedOrderListAction(payload))
                 }
@@ -288,7 +289,8 @@ class OrderRestClient @Inject constructor(
                     val orderError = wpAPINetworkErrorToOrderError(response.error)
                     val payload = FetchOrderListResponsePayload(
                         error = orderError,
-                        listDescriptor = listDescriptor
+                        listDescriptor = listDescriptor,
+                        networkingMode = response.networkingMode
                     )
                     dispatcher.dispatch(WCOrderActionBuilder.newFetchedOrderListAction(payload))
                 }
diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCOrderStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCOrderStore.kt
index bc2a456663e6..38ffb4674590 100644
--- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCOrderStore.kt
+++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCOrderStore.kt
@@ -22,6 +22,7 @@ import org.wordpress.android.fluxc.model.WCOrderSummaryModel
 import org.wordpress.android.fluxc.model.metadata.WCMetaData
 import org.wordpress.android.fluxc.network.BaseRequest.BaseNetworkError
 import org.wordpress.android.fluxc.network.BaseRequest.GenericErrorType.SERVER_ERROR
+import org.wordpress.android.fluxc.network.rest.wpapi.WPAPINetworkingMode
 import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooError
 import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooErrorType.API_ERROR
 import org.wordpress.android.fluxc.network.rest.wpcom.wc.WooResult
@@ -105,11 +106,13 @@ class WCOrderStore @Inject constructor(
         var loadedMore: Boolean = false,
         var canLoadMore: Boolean = false,
         val requestDurationMs: Long = 0,
+        val networkingMode: WPAPINetworkingMode? = null,
     ) : Payload<OrderError>() {
         constructor(
             error: OrderError,
-            listDescriptor: WCOrderListDescriptor
-        ) : this(listDescriptor, requestDurationMs = 0) {
+            listDescriptor: WCOrderListDescriptor,
+            networkingMode: WPAPINetworkingMode? = null,
+        ) : this(listDescriptor, networkingMode = networkingMode) {
             this.error = error
         }
     }
@@ -365,6 +368,7 @@ class WCOrderStore @Inject constructor(
     class OnOrderSummariesFetched(
         val listDescriptor: WCOrderListDescriptor,
         val duration: Long,
+        val networkingMode: WPAPINetworkingMode?,
         error: OrderError? = null
     ) : OnChanged<OrderError>() {
         init {
@@ -946,6 +950,7 @@ class WCOrderStore @Inject constructor(
             OnOrderSummariesFetched(
                 listDescriptor = payload.listDescriptor,
                 duration = payload.requestDurationMs,
+                networkingMode = payload.networkingMode,
                 error = payload.error
             )
         )
diff --git a/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetworkTest.kt b/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetworkTest.kt
index 3cb36b39cbfa..1ee7cb194c38 100644
--- a/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetworkTest.kt
+++ b/libs/fluxc-tests/src/test/java/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetworkTest.kt
@@ -3,19 +3,18 @@ package org.wordpress.android.fluxc.network.rest.wpcom.wc
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.argThat
 import org.mockito.kotlin.given
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 import org.robolectric.RobolectricTestRunner
-import org.wordpress.android.fluxc.Dispatcher
-import org.wordpress.android.fluxc.action.SiteAction
 import org.wordpress.android.fluxc.model.SiteModel
 import org.wordpress.android.fluxc.network.rest.wpapi.WPAPINetworkError
 import org.wordpress.android.fluxc.network.rest.wpapi.WPAPIResponse
 import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsNetwork
+import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsStore
 import org.wordpress.android.fluxc.network.rest.wpcom.JetpackTunnelWPAPINetwork
+import org.wordpress.android.fluxc.persistence.SiteSqlUtils
 import org.wordpress.android.fluxc.test
 
 @RunWith(RobolectricTestRunner::class)
@@ -24,12 +23,14 @@ class WooExperimentalNetworkTest {
     private val testPath = "path"
     private val applicationPasswordsNetwork: ApplicationPasswordsNetwork = mock()
     private val jetpackTunnelWPAPINetwork: JetpackTunnelWPAPINetwork = mock()
-    private val dispatcher: Dispatcher = mock()
+    private val applicationPasswordsStore: ApplicationPasswordsStore = mock()
+    private val siteSqlUtils: SiteSqlUtils = mock()
 
     private val sut = WooExperimentalNetwork(
         applicationPasswordsNetwork = applicationPasswordsNetwork,
         jetpackTunnelWPAPINetwork = jetpackTunnelWPAPINetwork,
-        dispatcher = dispatcher
+        applicationPasswordsStore = applicationPasswordsStore,
+        siteSqlUtils = siteSqlUtils
     )
 
     @Test
@@ -101,6 +102,9 @@ class WooExperimentalNetworkTest {
                     )
                 )
             )
+            givenJetpackTunnelResponse(
+                WPAPIResponse.Success(SampleResponse("value"))
+            )
 
             sut.executeGetGsonRequest(
                 site = testSite,
@@ -111,10 +115,7 @@ class WooExperimentalNetworkTest {
             val expectedSite = testSite.apply {
                 applicationPasswordsAuthorizeUrl = null
             }
-            verify(dispatcher).dispatch(argThat {
-                this.type == SiteAction.UPDATE_SITE &&
-                    this.payload == expectedSite
-            })
+            verify(siteSqlUtils).insertOrUpdateSite(expectedSite)
         }
 
     private suspend fun givenAppPasswordsResponse(response: WPAPIResponse<SampleResponse>) {
diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
index 5e131338afbd..f614b4b6e310 100644
--- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
+++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
@@ -1,6 +1,27 @@
 package org.wordpress.android.fluxc.network.rest.wpapi
 
 sealed class WPAPIResponse<T> {
-    data class Success<T>(val data: T?) : WPAPIResponse<T>()
-    data class Error<T>(val error: WPAPINetworkError) : WPAPIResponse<T>()
+    data class Success<T>(
+        val data: T?,
+        val networkingMode: WPAPINetworkingMode? = null,
+    ) : WPAPIResponse<T>()
+
+    data class Error<T>(
+        val error: WPAPINetworkError,
+        val networkingMode: WPAPINetworkingMode? = null,
+    ) : WPAPIResponse<T>()
+}
+
+/**
+ * The networking mode that was used to make the request.
+ *
+ * This is used for tracking purposes, and used only with the Woo experimental networking mode.
+ */
+sealed interface WPAPINetworkingMode {
+    data object ApplicationPasswords : WPAPINetworkingMode
+    data object ApplicationPasswordsWithJetpack : WPAPINetworkingMode
+    data class JetpackTunnel(
+        val isFallback: Boolean = false,
+        val applicationPasswordsError: WPAPINetworkError? = null,
+    ) : WPAPINetworkingMode
 }
diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/ApplicationPasswordsStore.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/ApplicationPasswordsStore.kt
index 6c8d75e614e5..42c3c2570693 100644
--- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/ApplicationPasswordsStore.kt
+++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/applicationpasswords/ApplicationPasswordsStore.kt
@@ -4,7 +4,6 @@ import android.content.Context
 import android.content.SharedPreferences
 import androidx.security.crypto.EncryptedSharedPreferences
 import androidx.security.crypto.MasterKeys
-import okhttp3.Credentials
 import org.wordpress.android.fluxc.model.SiteModel
 import org.wordpress.android.util.AppLog
 import org.wordpress.android.util.UrlUtils
@@ -22,16 +21,6 @@ class ApplicationPasswordsStore @Inject constructor(
         private const val UUID_PREFERENCE_KEY_PREFIX = "app_password_uuid_"
     }
 
-    /*
-    Exposed only to pass to React Native instance so we can authenticate via application password
-    there. Do not use directly in WCAndroid app.
-     */
-    fun getApplicationPasswordAuthHeader(site: SiteModel): String =
-        Credentials.basic(
-            username = encryptedPreferences.getString(site.usernamePrefKey, null).orEmpty(),
-            password = encryptedPreferences.getString(site.passwordPrefKey, null).orEmpty()
-        )
-
     @Inject internal lateinit var configuration: ApplicationPasswordsConfiguration
 
     private val applicationName: String
@@ -41,6 +30,11 @@ class ApplicationPasswordsStore @Inject constructor(
         initEncryptedPrefs()
     }
 
+    fun hasCredentials(site: SiteModel): Boolean {
+        return encryptedPreferences.contains(site.usernamePrefKey) &&
+            encryptedPreferences.contains(site.passwordPrefKey)
+    }
+
     @Synchronized
     internal fun getCredentials(site: SiteModel): ApplicationPasswordCredentials? {
         val username = encryptedPreferences.getString(site.usernamePrefKey, null)


Reviews:
user: login: hichamboushaba
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T11:49:02Z
user: login: hichamboushaba
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T11:49:39Z
user: login: hichamboushaba
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T11:50:53Z
user: login: irfano
body: LGTM! I just added a non-blocking comment. I also verified the expected results in all test cases.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T18:08:41Z
user: login: hichamboushaba
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T13:32:41Z
user: login: hichamboushaba
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-18T13:33:31Z
user: login: irfano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T15:48:21Z
user: login: irfano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T15:56:02Z

Review comments:
diff_hunk: @@ -597,13 +598,29 @@ class OrderListViewModel @Inject constructor(
     @SuppressWarnings("unused")
     @Subscribe(threadMode = MAIN)
     fun onOrderSummariesFetched(event: OnOrderSummariesFetched) {
+        fun WPAPINetworkingMode.toTrackingValue(): String {
+            return when (this) {
+                is WPAPINetworkingMode.ApplicationPasswords -> "app_passwords"
+                is WPAPINetworkingMode.ApplicationPasswordsWithJetpack -> "app_passwords_with_jetpack"
+                is WPAPINetworkingMode.JetpackTunnel -> "jetpack_tunnel"
+            }
+        }
+
         // Only track if this is not from a search query
         if (!event.listDescriptor.searchQuery.isNullOrEmpty()) {
             return
         }
 
         if (event.isError) {
-            AnalyticsTracker.track(AnalyticsEvent.ORDER_LIST_LOAD_ERROR)
+            AnalyticsTracker.track(
+                AnalyticsEvent.ORDER_LIST_LOAD_ERROR,
+                properties = mapOf(
+                    "request_type" to event.networkingMode?.toTrackingValue()
+                ).filterNotNull(),
+                errorType = event.error.type.name,
+                errorContext = this::class.simpleName,
+                errorDescription = event.error.message,
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
user: login: hichamboushaba
body: I added these properties to the event, I missed them when I fixed the tracking of this even in #13816 
created_at: 2025-04-15T11:49:02Z
author_association: MEMBER
diff_hunk: @@ -1,6 +1,27 @@
 package org.wordpress.android.fluxc.network.rest.wpapi
 
 sealed class WPAPIResponse<T> {
-    data class Success<T>(val data: T?) : WPAPIResponse<T>()
-    data class Error<T>(val error: WPAPINetworkError) : WPAPIResponse<T>()
+    data class Success<T>(
+        val data: T?,
+        val networkingMode: WPAPINetworkingMode? = null,
path: libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
user: login: hichamboushaba
body: This property will be just temporary, we'll remove it after collecting the experiment data
created_at: 2025-04-15T11:49:39Z
author_association: MEMBER
diff_hunk: @@ -120,16 +126,25 @@ class WooExperimentalNetwork @Inject constructor(
                 AppLog.T.API,
                 "Application Passwords not supported for site: ${site.url}, use Jetpack Tunnel"
             )
-            return jetpackTunnelWPAPINetwork.request()
+            return jetpackTunnelWPAPINetwork.request().copyWith(
+                networkingMode = WPAPINetworkingMode.JetpackTunnel()
+            )
         }
 
+        val hasAppPassword = applicationPasswordsStore.hasCredentials(site)
+
         return when (val appPasswordsResponse = applicationPasswordsNetwork.request()) {
             is WPAPIResponse.Success<*> -> {
                 AppLog.v(
                     AppLog.T.API,
                     "Request successful for site: ${site.url}, using Application Passwords"
                 )
-                appPasswordsResponse
+                (appPasswordsResponse as WPAPIResponse<T>).copyWith(
+                    // When creating a new Application Password, we don't want to track this request, as its duration
+                    // is not relevant to our experiment.
+                    // So we track only requests where we already have a password saved.
path: libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/WooExperimentalNetwork.kt
user: login: hichamboushaba
body: Let me know if this point needs further clarification.
created_at: 2025-04-15T11:50:53Z
author_association: MEMBER
diff_hunk: @@ -1,6 +1,27 @@
 package org.wordpress.android.fluxc.network.rest.wpapi
 
 sealed class WPAPIResponse<T> {
-    data class Success<T>(val data: T?) : WPAPIResponse<T>()
-    data class Error<T>(val error: WPAPINetworkError) : WPAPIResponse<T>()
+    data class Success<T>(
+        val data: T?,
+        val networkingMode: WPAPINetworkingMode? = null,
+    ) : WPAPIResponse<T>()
+
+    data class Error<T>(
+        val error: WPAPINetworkError,
+        val networkingMode: WPAPINetworkingMode? = null,
path: libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
user: login: irfano
body: WDYT about moving `networkingMode` parameter to `WPAPIResponse` superclass, since it's a common property shared by its subclasses?
created_at: 2025-04-17T17:59:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -614,9 +631,23 @@ class OrderListViewModel @Inject constructor(
                     mapOf(
                         AnalyticsTracker.KEY_TOTAL_DURATION to totalDurationInSeconds,
                         AnalyticsTracker.KEY_STATUS to event.listDescriptor.statusFilter,
-                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders
+                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders,
+                        "request_type" to event.networkingMode?.toTrackingValue()
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
user: login: irfano
body: I see you didn’t add a constant in AnalyticsTracker, and I assume the reason is that this is a temporary change, so having less code makes it easier to remove later.
created_at: 2025-04-17T18:03:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -614,9 +631,23 @@ class OrderListViewModel @Inject constructor(
                     mapOf(
                         AnalyticsTracker.KEY_TOTAL_DURATION to totalDurationInSeconds,
                         AnalyticsTracker.KEY_STATUS to event.listDescriptor.statusFilter,
-                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders
+                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders,
+                        "request_type" to event.networkingMode?.toTrackingValue()
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
user: login: hichamboushaba
body: > I assume the reason is that this is a temporary change, so having less code makes it easier to remove later.

True.

Related to this, I personally don't like that we define the keys in `AnalyticsTracker` itself, this could make sense for reusable keys, but for other keys that are used only in single screens (or flows), I think something like what the iOS team does would make more sense and be more scalable. But this is a topic for discussion later with the rest of the team.
created_at: 2025-04-18T13:32:40Z
author_association: MEMBER
diff_hunk: @@ -1,6 +1,27 @@
 package org.wordpress.android.fluxc.network.rest.wpapi
 
 sealed class WPAPIResponse<T> {
-    data class Success<T>(val data: T?) : WPAPIResponse<T>()
-    data class Error<T>(val error: WPAPINetworkError) : WPAPIResponse<T>()
+    data class Success<T>(
+        val data: T?,
+        val networkingMode: WPAPINetworkingMode? = null,
+    ) : WPAPIResponse<T>()
+
+    data class Error<T>(
+        val error: WPAPINetworkError,
+        val networkingMode: WPAPINetworkingMode? = null,
path: libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
user: login: hichamboushaba
body: Good point, but as I was just on my phone yesterday, I decided to merge the PR without applying it.

This property will be just temporary during the experiment period, so the impact of the change is small.
created_at: 2025-04-18T13:33:30Z
author_association: MEMBER
diff_hunk: @@ -1,6 +1,27 @@
 package org.wordpress.android.fluxc.network.rest.wpapi
 
 sealed class WPAPIResponse<T> {
-    data class Success<T>(val data: T?) : WPAPIResponse<T>()
-    data class Error<T>(val error: WPAPINetworkError) : WPAPIResponse<T>()
+    data class Success<T>(
+        val data: T?,
+        val networkingMode: WPAPINetworkingMode? = null,
+    ) : WPAPIResponse<T>()
+
+    data class Error<T>(
+        val error: WPAPINetworkError,
+        val networkingMode: WPAPINetworkingMode? = null,
path: libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/WPAPIResponse.kt
user: login: irfano
body: I’m fine with the current solution 👍🏻 It’ll be removed soon anyway.
created_at: 2025-04-18T15:48:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -614,9 +631,23 @@ class OrderListViewModel @Inject constructor(
                     mapOf(
                         AnalyticsTracker.KEY_TOTAL_DURATION to totalDurationInSeconds,
                         AnalyticsTracker.KEY_STATUS to event.listDescriptor.statusFilter,
-                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders
+                        AnalyticsTracker.KEY_TOTAL_COMPLETED_ORDERS to totalCompletedOrders,
+                        "request_type" to event.networkingMode?.toTrackingValue()
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListViewModel.kt
user: login: irfano
body: Good to know. I completely agree with that as well.
created_at: 2025-04-18T15:56:01Z
author_association: CONTRIBUTOR

PR comments:
user: login: dangermattic
created_at: 2025-04-14T16:07:42Z
body: <!--
  0 Errors
  1 Warning: This PR is assigned to the mil...
  1 Message: <p>This PR contains changes to...
  0 Markdowns
-->
<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true" data-kind="Warning">
          1 Warning
      </th>
     </tr>
  </thead>
  <tbody>
    <tr>
      <td>:warning:</td>
      <td data-sticky="false">This PR is assigned to the milestone <a href="https://github.com/woocommerce/woocommerce-android/milestone/256">22.2</a>. This milestone is due in less than 2 days.<br />
Please make sure to get it merged by then or assign it to a milestone with a later deadline.</td>
    </tr>
  </tbody>
</table>
<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true" data-kind="Message">
          1 Message
      </th>
     </tr>
  </thead>
  <tbody>
    <tr>
      <td>:book:</td>
      <td data-sticky="false"><p>This PR contains changes to Tracks-related logic. Please ensure (<strong>author and reviewer</strong>) the following are completed:</p>

<ul>
  <li>The tracks events must be validated in the Tracks system.</li>
  <li>Verify the internal Tracks spreadsheet has also been updated.</li>
  <li>Please consider registering any new events.</li>
  <li>The PR must be assigned the <strong>category: tracks</strong> label.</li>
</ul></td>
    </tr>
  </tbody>
</table>

<p align="right" data-meta="generated_by_pr-check">
  Generated by :no_entry_sign: <a href="https://danger.systems/">Danger</a>
</p>

author_association: COLLABORATOR
user: login: wpmobilebot
created_at: 2025-04-14T16:16:20Z
body: <!-- REUSE_ID: WooCommerce-Wear-prototype-build-link -->

<details><summary>📲 You can test the changes from this Pull Request in <b>WooCommerce-Wear Android</b> by scanning the QR code below to install the corresponding build.</summary>
<table>
<tr>
  <td rowspan='6' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2F70ad533e752cbd4dcd515ed6cfb9f823d6f9f819%2Fwoocommerce-wear-prototype-build-pr13919-870b65d.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> WooCommerce-Wear Android</td>
</tr>
<tr><td><b>Platform</b></td><td>⌚️ Wear OS</td></tr>
<tr><td><b>Flavor</b></td><td>Jalapeno</td></tr>
<tr><td><b>Build Type</b></td><td>Debug</td></tr>
<tr><td><b>Commit</b></td><td>870b65d7ba6f6deacea23cd7463588676b8bdb31</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/70ad533e752cbd4dcd515ed6cfb9f823d6f9f819/woocommerce-wear-prototype-build-pr13919-870b65d.apk'><code>woocommerce-wear-prototype-build-pr13919-870b65d.apk</code></a></td></tr>
</table>

</details>

author_association: COLLABORATOR
user: login: wpmobilebot
created_at: 2025-04-14T16:25:18Z
body: <!-- REUSE_ID: WooCommerce-prototype-build-link -->

<p>📲 You can test the changes from this Pull Request in <b>WooCommerce Android</b> by scanning the QR code below to install the corresponding build.</p>
<table>
<tr>
  <td rowspan='6' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2Fa8b6e90d9d969f1b573b3c0dd090796761ee106e%2Fwoocommerce-prototype-build-pr13919-870b65d.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> WooCommerce Android</td>
</tr>
<tr><td><b>Platform</b></td><td>📱 Mobile</td></tr>
<tr><td><b>Flavor</b></td><td>Jalapeno</td></tr>
<tr><td><b>Build Type</b></td><td>Debug</td></tr>
<tr><td><b>Commit</b></td><td>870b65d7ba6f6deacea23cd7463588676b8bdb31</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/a8b6e90d9d969f1b573b3c0dd090796761ee106e/woocommerce-prototype-build-pr13919-870b65d.apk'><code>woocommerce-prototype-build-pr13919-870b65d.apk</code></a></td></tr>
</table>


author_association: COLLABORATOR
user: login: codecov-commenter
created_at: 2025-04-14T17:34:29Z
body: ## [Codecov](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) Report
:x: Patch coverage is `13.84615%` with `56 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 38.24%. Comparing base ([`f995216`](https://app.codecov.io/gh/woocommerce/woocommerce-android/commit/f9952162c3ce3014259d02d2e2ee8b9072dd9846?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce)) to head ([`870b65d`](https://app.codecov.io/gh/woocommerce/woocommerce-android/commit/870b65d7ba6f6deacea23cd7463588676b8bdb31?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce)).
:warning: Report is 11286 commits behind head on trunk.

| [Files with missing lines](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) | Patch % | Lines |
|---|---|---|
| [...merce/android/ui/orders/list/OrderListViewModel.kt](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&filepath=WooCommerce%2Fsrc%2Fmain%2Fkotlin%2Fcom%2Fwoocommerce%2Fandroid%2Fui%2Forders%2Flist%2FOrderListViewModel.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce#diff-V29vQ29tbWVyY2Uvc3JjL21haW4va290bGluL2NvbS93b29jb21tZXJjZS9hbmRyb2lkL3VpL29yZGVycy9saXN0L09yZGVyTGlzdFZpZXdNb2RlbC5rdA==) | 0.00% | [22 Missing :warning: ](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) |
| [...xc/network/rest/wpcom/wc/WooExperimentalNetwork.kt](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&filepath=libs%2Ffluxc-plugin%2Fsrc%2Fmain%2Fkotlin%2Forg%2Fwordpress%2Fandroid%2Ffluxc%2Fnetwork%2Frest%2Fwpcom%2Fwc%2FWooExperimentalNetwork.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce#diff-bGlicy9mbHV4Yy1wbHVnaW4vc3JjL21haW4va290bGluL29yZy93b3JkcHJlc3MvYW5kcm9pZC9mbHV4Yy9uZXR3b3JrL3Jlc3Qvd3Bjb20vd2MvV29vRXhwZXJpbWVudGFsTmV0d29yay5rdA==) | 0.00% | [19 Missing :warning: ](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) |
| [.../org/wordpress/android/fluxc/store/WCOrderStore.kt](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&filepath=libs%2Ffluxc-plugin%2Fsrc%2Fmain%2Fkotlin%2Forg%2Fwordpress%2Fandroid%2Ffluxc%2Fstore%2FWCOrderStore.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce#diff-bGlicy9mbHV4Yy1wbHVnaW4vc3JjL21haW4va290bGluL29yZy93b3JkcHJlc3MvYW5kcm9pZC9mbHV4Yy9zdG9yZS9XQ09yZGVyU3RvcmUua3Q=) | 0.00% | [5 Missing :warning: ](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) |
| [...uxc/network/rest/wpcom/wc/order/OrderRestClient.kt](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&filepath=libs%2Ffluxc-plugin%2Fsrc%2Fmain%2Fkotlin%2Forg%2Fwordpress%2Fandroid%2Ffluxc%2Fnetwork%2Frest%2Fwpcom%2Fwc%2Forder%2FOrderRestClient.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce#diff-bGlicy9mbHV4Yy1wbHVnaW4vc3JjL21haW4va290bGluL29yZy93b3JkcHJlc3MvYW5kcm9pZC9mbHV4Yy9uZXR3b3JrL3Jlc3Qvd3Bjb20vd2Mvb3JkZXIvT3JkZXJSZXN0Q2xpZW50Lmt0) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) |
| [.../android/fluxc/network/rest/wpapi/WPAPIResponse.kt](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&filepath=libs%2Ffluxc%2Fsrc%2Fmain%2Fjava%2Forg%2Fwordpress%2Fandroid%2Ffluxc%2Fnetwork%2Frest%2Fwpapi%2FWPAPIResponse.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce#diff-bGlicy9mbHV4Yy9zcmMvbWFpbi9qYXZhL29yZy93b3JkcHJlc3MvYW5kcm9pZC9mbHV4Yy9uZXR3b3JrL3Jlc3Qvd3BhcGkvV1BBUElSZXNwb25zZS5rdA==) | 66.66% | [4 Missing :warning: ](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) |
| [.../applicationpasswords/ApplicationPasswordsStore.kt](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&filepath=libs%2Ffluxc%2Fsrc%2Fmain%2Fjava%2Forg%2Fwordpress%2Fandroid%2Ffluxc%2Fnetwork%2Frest%2Fwpapi%2Fapplicationpasswords%2FApplicationPasswordsStore.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce#diff-bGlicy9mbHV4Yy9zcmMvbWFpbi9qYXZhL29yZy93b3JkcHJlc3MvYW5kcm9pZC9mbHV4Yy9uZXR3b3JrL3Jlc3Qvd3BhcGkvYXBwbGljYXRpb25wYXNzd29yZHMvQXBwbGljYXRpb25QYXNzd29yZHNTdG9yZS5rdA==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce) |

<details><summary>Additional details and impacted files</summary>



```diff
@@             Coverage Diff              @@
##              trunk   #13919      +/-   ##
============================================
- Coverage     38.25%   38.24%   -0.02%     
+ Complexity     9361     9360       -1     
============================================
  Files          2091     2091              
  Lines        115572   115620      +48     
  Branches      14780    14791      +11     
============================================
+ Hits          44215    44221       +6     
- Misses        67331    67372      +41     
- Partials       4026     4027       +1     
```
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/woocommerce/woocommerce-android/pull/13919?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=woocommerce).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: hichamboushaba
created_at: 2025-04-17T22:41:13Z
body: Thanks @irfano for the review, to get this ready for the code freeze, I'm merging it now.

I'll reply to your comments tomorrow morning.
author_association: MEMBER