Skip to content

Commit 2ae0e03

Browse files
oguzkoceradalpari
andauthored
Integrate Chucker for network troubleshooting (#22381)
* Add Chucker integration research documentation Document the plan for integrating Chucker HTTP inspector into the app for production use (disabled by default, user opt-in for troubleshooting). Research findings: - 7 network paths identified, 5 are OkHttp-based (interceptable) - OkHttpClientModule is the main path covering ~90% of traffic - 2 HttpURLConnection usages cannot be intercepted (edge cases) - Wrapper interceptor approach recommended for runtime enable/disable * Add track network requests interceptor foundation for Chucker integration Add an OkHttp interceptor that will serve as the foundation for HTTP traffic inspection via Chucker. The interceptor is disabled by default and can be toggled by users in the Help screen for troubleshooting. Changes: - Add `TrackNetworkRequestsInterceptor` in FluxC with preference interface - Add `TrackNetworkRequestsModule` to inject interceptor into OkHttpClient - Add preference `IS_TRACK_NETWORK_REQUESTS_ENABLED` in `AppPrefs` - Add toggle UI in Help screen below "Contact email" * Integrate Chucker library into TrackNetworkRequestsInterceptor Add Chucker v4.2.0 as the HTTP traffic inspection backend for the Track Network Requests feature. The interceptor now delegates to ChuckerInterceptor when the feature is enabled. Configuration: - 1-hour retention period - Sensitive headers redacted (Authorization, Cookie, Set-Cookie, X-WP-Nonce) - Notifications disabled (custom UI entry point to be added) - Max content length: 250KB Changes: - Add Chucker dependency to libs.versions.toml and build.gradle files - Update TrackNetworkRequestsInterceptor to create and use ChuckerInterceptor - Simplify TrackNetworkRequestsModule (Chucker is now an implementation detail) * Add View Network Requests button to launch Chucker UI Add a button in the Help screen that launches Chucker's traffic inspector UI. The button is only visible when Track Network Requests is enabled. Changes: - Add `viewNetworkRequestsButton` to help_activity.xml - Add `view_network_requests` string resource - Update `setupTrackNetworkRequestsToggle()` to show/hide button and handle click * Add configurable retention period and dialog-based UX for network tracking Implement retention period selection when enabling Track Network Requests, with dialog-based enable/disable flow for better user communication. Features: - Retention period options: 1 Hour, 1 Day, 1 Week, Until Cleared - Enable dialog with privacy explanation and retention picker - Disable dialog with guidance on clearing logs manually - "View Network Requests" now shows current retention period Technical changes: - Add `NetworkRequestsRetentionPeriod` enum with stable int values for persistence - Interceptor lazily creates and recreates ChuckerInterceptor when retention changes - Add `dialog_title_with_message.xml` layout for dialogs with description text - Use `setSwitchCheckedSilently()` to prevent listener loops on cancel Changes: - Add retention period preference to `AppPrefs` and `AppPrefsWrapper` - Update `TrackNetworkRequestsPreference` interface with `getRetentionPeriod()` - Add enable/disable dialogs to `HelpActivity` - Update `help_activity.xml` with container showing retention info - Add string resources for retention options and dialog messages * Add login request tracking and make preferences device-level Enable network request tracking for OAuth login flows by adding interceptors to WPcomLoginClient. Move tracking preferences to UndeletablePrefKey so they persist across logout/login cycles, allowing users to troubleshoot login issues. Changes: - Add interceptors to WPcomLoginClient for OAuth token exchange - Move IS_TRACK_NETWORK_REQUESTS_ENABLED to UndeletablePrefKey - Move TRACK_NETWORK_REQUESTS_RETENTION_PERIOD to UndeletablePrefKey - Add documentation for Chucker export API options * Add GutenbergKit network request logging to Chucker Integrates GutenbergKit's WebView network requests with Chucker by replaying them through OkHttp. This allows editor network traffic to appear alongside native app requests in the unified log. Changes: - Add `GutenbergKitNetworkLogger` to replay JS fetch requests through OkHttp - Add `isNetworkLoggingEnabled` to `FeatureConfig` and `EditorConfiguration` - Wire up `NetworkRequestListener` in `GutenbergKitActivity` - Update GutenbergKit to PR #238 branch with network logging support * Remove debug logs from GutenbergKit network logging Remove verbose debug logging used during development. Keep error logging for diagnosing failures. * Add experimental feature flag for Network Debugging Hide the Track Network Requests section in Help behind an experimental feature flag. When the flag is disabled, the section is completely hidden. When trying to disable the flag while tracking is enabled, show an error dialog instructing the user to disable tracking first. Changes: - Add NETWORK_DEBUGGING to ExperimentalFeatures.Feature enum - Add validation in ExperimentalFeaturesViewModel to prevent disabling the flag while tracking is active - Add NetworkDebuggingErrorDialog composable for the error state - Update HelpActivity to check the feature flag before showing section - Wrap track network requests UI in a single container for cleaner visibility management * Fix detekt issues in Chucker integration code Add suppress annotations for: - LongMethod in ExperimentalFeaturesActivity.onCreate (Compose UI) - TooGenericExceptionCaught in GutenbergKitNetworkLogger (intentional) - MagicNumber in GutenbergKitNetworkLogger (HTTP status codes) * Add network debugging to new Support screen Duplicate the network tracking functionality from HelpActivity to the new Compose-based SupportActivity (behind MODERN_SUPPORT flag). Also update help_activity.xml to show retention info under the toggle instead of under the view button, for consistency between both UIs. Changes: - Add NetworkTrackingState and DialogEvent to SupportViewModel - Add NetworkTrackingToggleItem composable to SupportScreen - Add dialog handling and Chucker navigation to SupportActivity - Restructure help_activity.xml for consistent retention info placement * Integrate Chucker interceptor into wordpress-rs clients Wire `TrackNetworkRequestsInterceptor` into all wordpress-rs based network clients so their requests are captured when network debugging is enabled. Changes: - `WpApiClientProvider`: Accept interceptors via Dagger `@Named` set - `ApplicationModule`: Pass interceptor to `WpLoginClient` - `DataViewViewModel`: Inject interceptor for `WpComApiClient` - `JetpackConnectionHelper`: Inject interceptor for connection client - `AddSubscribersViewModel`: Inject interceptor for subscriber API - Child ViewModels: Pass interceptor to parent `DataViewViewModel` * Update GutenbergKit to v0.11.0 and adapt to API changes GutenbergKit v0.11.0 renamed `NetworkRequest` to `RecordedNetworkRequest` and added a `statusText` field, allowing us to remove our custom HTTP status code mapping. Changes: - Update GutenbergKit version to v0.11.0 - Rename NetworkRequest to RecordedNetworkRequest - Use statusText from RecordedNetworkRequest instead of toStatusMessage() * Use OkHttpClientQualifiers.INTERCEPTORS constant for @nAmed annotations Replace hardcoded "interceptors" strings with the constant to prevent typos and enable easier refactoring. * Remove research documentation files * Fix lint warnings and update tests for new constructor parameters - Add @nonnull annotation to ApplicationModule parameter - Add @SuppressLint("InflateParams") for AlertDialog custom title inflation - Update SupportViewModelTest with appPrefsWrapper and experimentalFeatures - Update DataViewViewModelTest with trackNetworkRequestsInterceptor - Update ExperimentalFeaturesViewModelTest with appPrefsWrapper - Update TermsViewModelTest with trackNetworkRequestsInterceptor * Refactor retention period display string into two methods Split getRetentionPeriodDisplayString into separate methods for better readability and reusability: - getRetentionPeriodStringRes: returns the string resource ID - getRetentionPeriodDisplayString: resolves it to a String Changes: - Extract resource ID mapping to dedicated method - Simplify the display string method to delegate Co-authored-by: Adalberto Plaza <[email protected]> * Migrate network tracking dialogs to Compose Replace View-based AlertDialogs with Compose AlertDialogs in SupportScreen. This addresses PR feedback about using Compose dialogs in a Compose activity. Changes: - Add reusable SingleChoiceAlertDialog component - Convert DialogEvent (SharedFlow) to DialogState (StateFlow) in ViewModel - Add EnableTrackingDialog and DisableTrackingDialog composables - Remove XML layout inflation and View-based dialog code from Activity * Add tests for network tracking functionality in SupportViewModel Cover the new network tracking dialog state management with tests for: - init() network debugging state based on feature flag and preferences - onNetworkTrackingToggle showing enable/disable dialogs - onEnableTrackingConfirmed/onDisableTrackingConfirmed state updates - onDialogDismissed without side effects - onRetentionPeriodSelected updating dialog state Changes: - Add 10 new tests in SupportViewModelTest - Move init-related tests to existing init() region - Create new region for dialog interaction tests * Update GutenbergKit to v0.11.1 --------- Co-authored-by: Adalberto Plaza <[email protected]>
1 parent 9b5ab17 commit 2ae0e03

37 files changed

+1366
-44
lines changed

WordPress/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ dependencies {
452452
implementation(libs.greenrobot.eventbus.main)
453453
implementation(libs.greenrobot.eventbus.java)
454454
implementation(libs.squareup.retrofit)
455+
implementation(libs.chucker)
455456
implementation(libs.apache.commons.text)
456457
implementation(libs.airbnb.lottie.main)
457458
implementation(libs.facebook.shimmer)

WordPress/src/main/java/org/wordpress/android/modules/ApplicationModule.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.content.SharedPreferences;
66
import android.hardware.SensorManager;
77

8+
import androidx.annotation.NonNull;
89
import androidx.lifecycle.LiveData;
910
import androidx.preference.PreferenceManager;
1011

@@ -36,6 +37,7 @@
3637
import org.wordpress.android.util.config.InAppUpdatesFeatureConfig;
3738
import org.wordpress.android.util.config.RemoteConfigWrapper;
3839
import org.wordpress.android.util.wizard.WizardManager;
40+
import org.wordpress.android.fluxc.network.TrackNetworkRequestsInterceptor;
3941
import org.wordpress.android.viewmodel.helpers.ConnectionStatus;
4042
import org.wordpress.android.viewmodel.helpers.ConnectionStatusLiveData;
4143

@@ -153,7 +155,9 @@ public static RecordingStrategy provideVoiceToContentRecordingStrategy() {
153155
}
154156

155157
@Provides
156-
public static WpLoginClient provideWpLoginClient() {
157-
return new WpLoginClient(Collections.emptyList());
158+
public static WpLoginClient provideWpLoginClient(
159+
@NonNull TrackNetworkRequestsInterceptor trackNetworkRequestsInterceptor
160+
) {
161+
return new WpLoginClient(Collections.singletonList(trackNetworkRequestsInterceptor));
158162
}
159163
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.wordpress.android.modules
2+
3+
import android.content.Context
4+
import dagger.Module
5+
import dagger.Provides
6+
import dagger.hilt.InstallIn
7+
import dagger.hilt.android.qualifiers.ApplicationContext
8+
import dagger.hilt.components.SingletonComponent
9+
import dagger.multibindings.IntoSet
10+
import okhttp3.Interceptor
11+
import org.wordpress.android.fluxc.module.OkHttpClientQualifiers
12+
import org.wordpress.android.fluxc.network.NetworkRequestsRetentionPeriod
13+
import org.wordpress.android.fluxc.network.TrackNetworkRequestsInterceptor
14+
import org.wordpress.android.fluxc.network.TrackNetworkRequestsPreference
15+
import org.wordpress.android.ui.posts.editor.GutenbergKitNetworkLogger
16+
import org.wordpress.android.ui.prefs.AppPrefsWrapper
17+
import javax.inject.Named
18+
import javax.inject.Singleton
19+
20+
@InstallIn(SingletonComponent::class)
21+
@Module
22+
class TrackNetworkRequestsModule {
23+
@Singleton
24+
@Provides
25+
fun provideTrackNetworkRequestsPreference(appPrefsWrapper: AppPrefsWrapper): TrackNetworkRequestsPreference {
26+
return object : TrackNetworkRequestsPreference {
27+
override fun isEnabled(): Boolean = appPrefsWrapper.isTrackNetworkRequestsEnabled
28+
override fun getRetentionPeriod(): NetworkRequestsRetentionPeriod =
29+
NetworkRequestsRetentionPeriod.fromInt(appPrefsWrapper.trackNetworkRequestsRetentionPeriod)
30+
}
31+
}
32+
33+
@Singleton
34+
@Provides
35+
fun provideTrackNetworkRequestsInterceptor(
36+
@ApplicationContext context: Context,
37+
preference: TrackNetworkRequestsPreference
38+
): TrackNetworkRequestsInterceptor {
39+
return TrackNetworkRequestsInterceptor(context, preference)
40+
}
41+
42+
@Provides
43+
@IntoSet
44+
@Named(OkHttpClientQualifiers.INTERCEPTORS)
45+
fun provideTrackNetworkRequestsInterceptorAsInterceptor(
46+
interceptor: TrackNetworkRequestsInterceptor
47+
): Interceptor = interceptor
48+
49+
@Singleton
50+
@Provides
51+
fun provideGutenbergKitNetworkLogger(
52+
interceptor: TrackNetworkRequestsInterceptor
53+
): GutenbergKitNetworkLogger {
54+
return GutenbergKitNetworkLogger(interceptor)
55+
}
56+
}

WordPress/src/main/java/org/wordpress/android/support/main/ui/SupportActivity.kt

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ import androidx.core.net.toUri
1414
import androidx.lifecycle.Lifecycle
1515
import androidx.lifecycle.lifecycleScope
1616
import androidx.lifecycle.repeatOnLifecycle
17+
import com.chuckerteam.chucker.api.Chucker
1718
import dagger.hilt.android.AndroidEntryPoint
1819
import kotlinx.coroutines.launch
1920
import org.wordpress.android.BuildConfig
21+
import org.wordpress.android.R
22+
import org.wordpress.android.WordPress
2023
import org.wordpress.android.analytics.AnalyticsTracker
2124
import org.wordpress.android.analytics.AnalyticsTracker.Stat
22-
import org.wordpress.android.WordPress
25+
import org.wordpress.android.fluxc.network.NetworkRequestsRetentionPeriod
2326
import org.wordpress.android.support.aibot.ui.AIBotSupportActivity
24-
import org.wordpress.android.support.logs.ui.LogsActivity
2527
import org.wordpress.android.support.he.ui.HESupportActivity
28+
import org.wordpress.android.support.logs.ui.LogsActivity
2629
import org.wordpress.android.ui.ActivityLauncher
2730
import org.wordpress.android.ui.compose.theme.AppThemeM3
2831

@@ -47,6 +50,8 @@ class SupportActivity : AppCompatActivity() {
4750
val userInfo by viewModel.userInfo.collectAsState()
4851
val optionsVisibility by viewModel.optionsVisibility.collectAsState()
4952
val isLoggedIn by viewModel.isLoggedIn.collectAsState()
53+
val networkTrackingState by viewModel.networkTrackingState.collectAsState()
54+
val dialogState by viewModel.dialogState.collectAsState()
5055
AppThemeM3 {
5156
SupportScreen(
5257
userName = userInfo.userName,
@@ -55,20 +60,50 @@ class SupportActivity : AppCompatActivity() {
5560
isLoggedIn = isLoggedIn,
5661
showAskTheBots = optionsVisibility.showAskTheBots,
5762
showAskHappinessEngineers = optionsVisibility.showAskHappinessEngineers,
63+
showNetworkDebugging = networkTrackingState.showNetworkDebugging,
64+
isNetworkTrackingEnabled = networkTrackingState.isTrackingEnabled,
65+
networkTrackingRetentionInfo = getRetentionInfoText(
66+
networkTrackingState.retentionPeriod
67+
),
5868
versionName = WordPress.versionName,
69+
dialogState = dialogState,
5970
onBackClick = { finish() },
6071
onLoginClick = { viewModel.onLoginClick() },
6172
onHelpCenterClick = { viewModel.onHelpCenterClick() },
6273
onAskTheBotsClick = { viewModel.onAskTheBotsClick() },
6374
onAskHappinessEngineersClick = { viewModel.onAskHappinessEngineersClick() },
64-
onApplicationLogsClick = { viewModel.onApplicationLogsClick() }
75+
onApplicationLogsClick = { viewModel.onApplicationLogsClick() },
76+
onNetworkTrackingToggle = { viewModel.onNetworkTrackingToggle(it) },
77+
onViewNetworkRequestsClick = { viewModel.onViewNetworkRequestsClick() },
78+
onRetentionPeriodSelected = { viewModel.onRetentionPeriodSelected(it) },
79+
onEnableTrackingConfirmed = { viewModel.onEnableTrackingConfirmed(it) },
80+
onDisableTrackingConfirmed = { viewModel.onDisableTrackingConfirmed() },
81+
onDialogDismissed = { viewModel.onDialogDismissed() },
6582
)
6683
}
6784
}
6885
}
6986
)
7087
}
7188

89+
private fun getRetentionInfoText(period: NetworkRequestsRetentionPeriod): String {
90+
val periodString = getRetentionPeriodDisplayString(period)
91+
return getString(R.string.network_requests_retention_info, periodString)
92+
}
93+
94+
private fun getRetentionPeriodDisplayString(period: NetworkRequestsRetentionPeriod): String {
95+
return getString(getRetentionPeriodStringRes(period))
96+
}
97+
98+
private fun getRetentionPeriodStringRes(period: NetworkRequestsRetentionPeriod): Int {
99+
return when (period) {
100+
NetworkRequestsRetentionPeriod.ONE_HOUR -> R.string.network_requests_retention_one_hour
101+
NetworkRequestsRetentionPeriod.ONE_DAY -> R.string.network_requests_retention_one_day
102+
NetworkRequestsRetentionPeriod.ONE_WEEK -> R.string.network_requests_retention_one_week
103+
NetworkRequestsRetentionPeriod.FOREVER -> R.string.network_requests_retention_until_cleared
104+
}
105+
}
106+
72107
private fun observeNavigationEvents() {
73108
lifecycleScope.launch {
74109
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -81,6 +116,9 @@ class SupportActivity : AppCompatActivity() {
81116
is SupportViewModel.NavigationEvent.NavigateToAskHappinessEngineers -> {
82117
navigateToAskTheHappinessEngineers()
83118
}
119+
is SupportViewModel.NavigationEvent.NavigateToNetworkRequests -> {
120+
navigateToNetworkRequests()
121+
}
84122
}
85123
}
86124
}
@@ -120,6 +158,10 @@ class SupportActivity : AppCompatActivity() {
120158
startActivity(LogsActivity.createIntent(this))
121159
}
122160

161+
private fun navigateToNetworkRequests() {
162+
startActivity(Chucker.getLaunchIntent(this))
163+
}
164+
123165
companion object {
124166
@JvmStatic
125167
fun createIntent(context: Context): Intent = Intent(context, SupportActivity::class.java)

0 commit comments

Comments
 (0)