Skip to content

Commit 5ca3dbd

Browse files
authored
[Android] Increase in 5xx errors (#5847)
Task/Issue URL: https://app.asana.com/0/1200581511062568/1209741376363526/f ### Description Added more params to the m_webview_received_http_error_5xx_daily pixel. ### Steps to test this PR _HTTP Error Tracking_ - [ ] Generate a 5xx error and verify the error count is incremented in SharedPreferences - [ ] Verify the error details (status code, PPRo VPN status, External VPN status, WebView version) are correctly captured - [ ] Trigger the daily reporting worker and confirm 5xx error pixels are fired with the correct parameters - [ ] Verify the counter is reset after firing the pixel - [ ] Confirm pixels aren't fired again until the time window (24 hours) has elapsed ### UI changes NO UI changes.
1 parent c6ee032 commit 5ca3dbd

File tree

7 files changed

+296
-18
lines changed

7 files changed

+296
-18
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4945,10 +4945,10 @@ class BrowserTabViewModelTest {
49454945
}
49464946

49474947
@Test
4948-
fun whenPageIsChangedWithHttpError5XXThenUpdateCountPixelCalledForWebViewReceivedHttpError5XXDaily() = runTest {
4948+
fun whenPageIsChangedWithHttpError5XXThenUpdate5xxCountPixelCalledForWebViewReceivedHttpError5XXDaily() = runTest {
49494949
testee.recordHttpErrorCode(statusCode = 504, url = "example2.com")
49504950

4951-
verify(mockHttpErrorPixels).updateCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY)
4951+
verify(mockHttpErrorPixels).update5xxCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY, 504)
49524952
}
49534953

49544954
@Test

app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3438,19 +3438,22 @@ class BrowserTabViewModel @Inject constructor(
34383438
}
34393439

34403440
private fun updateHttpErrorCount(statusCode: Int) {
3441-
when {
3442-
// 400 errors
3443-
statusCode == HTTP_STATUS_CODE_BAD_REQUEST_ERROR -> httpErrorPixels.get().updateCountPixel(
3444-
HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_400_DAILY,
3445-
)
3446-
// all 4xx errors apart from 400
3447-
statusCode / 100 == HTTP_STATUS_CODE_CLIENT_ERROR_PREFIX -> httpErrorPixels.get().updateCountPixel(
3448-
HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_4XX_DAILY,
3449-
)
3450-
// all 5xx errors
3451-
statusCode / 100 == HTTP_STATUS_CODE_SERVER_ERROR_PREFIX -> httpErrorPixels.get().updateCountPixel(
3452-
HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY,
3453-
)
3441+
viewModelScope.launch(dispatchers.io()) {
3442+
when {
3443+
// 400 errors
3444+
statusCode == HTTP_STATUS_CODE_BAD_REQUEST_ERROR -> httpErrorPixels.get().updateCountPixel(
3445+
HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_400_DAILY,
3446+
)
3447+
// all 4xx errors apart from 400
3448+
statusCode / 100 == HTTP_STATUS_CODE_CLIENT_ERROR_PREFIX -> httpErrorPixels.get().updateCountPixel(
3449+
HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_4XX_DAILY,
3450+
)
3451+
// all 5xx errors
3452+
statusCode / 100 == HTTP_STATUS_CODE_SERVER_ERROR_PREFIX -> httpErrorPixels.get().update5xxCountPixel(
3453+
HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY,
3454+
statusCode,
3455+
)
3456+
}
34543457
}
34553458
}
34563459

app/src/main/java/com/duckduckgo/app/browser/httperrors/HttpErrorDailyReportingWorker.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class HttpErrorDailyReportingWorker(context: Context, workerParameters: WorkerPa
4848
return withContext(dispatchers.io()) {
4949
httpErrorPixels.fireCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_400_DAILY)
5050
httpErrorPixels.fireCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_4XX_DAILY)
51-
httpErrorPixels.fireCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY)
51+
httpErrorPixels.fire5xxCountPixels()
5252
return@withContext Result.success()
5353
}
5454
}

app/src/main/java/com/duckduckgo/app/browser/httperrors/HttpErrorPixels.kt

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,74 @@ package com.duckduckgo.app.browser.httperrors
1919
import android.content.Context
2020
import android.content.SharedPreferences
2121
import androidx.core.content.edit
22+
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
2223
import com.duckduckgo.app.statistics.pixels.Pixel
24+
import com.duckduckgo.browser.api.WebViewVersionProvider
2325
import com.duckduckgo.di.scopes.AppScope
26+
import com.duckduckgo.mobile.android.vpn.network.ExternalVpnDetector
27+
import com.duckduckgo.networkprotection.api.NetworkProtectionState
2428
import com.squareup.anvil.annotations.ContributesBinding
2529
import java.time.Instant
2630
import java.util.concurrent.TimeUnit
2731
import javax.inject.Inject
2832

2933
interface HttpErrorPixels {
3034
fun updateCountPixel(httpErrorPixelName: HttpErrorPixelName)
35+
suspend fun update5xxCountPixel(httpErrorPixelName: HttpErrorPixelName, statusCode: Int)
3136
fun fireCountPixel(httpErrorPixelName: HttpErrorPixelName)
37+
fun fire5xxCountPixels()
3238
}
3339

3440
@ContributesBinding(AppScope::class)
3541
class RealHttpErrorPixels @Inject constructor(
3642
private val pixel: Pixel,
3743
private val context: Context,
44+
private val webViewVersionProvider: WebViewVersionProvider,
45+
private val networkProtectionState: NetworkProtectionState,
46+
private val externalVpnDetector: ExternalVpnDetector,
47+
private val androidBrowserConfig: AndroidBrowserConfigFeature,
3848
) : HttpErrorPixels {
3949

4050
private val preferences: SharedPreferences by lazy { context.getSharedPreferences(FILENAME, Context.MODE_PRIVATE) }
51+
private val pixel5xxKeys: MutableSet<String> by lazy {
52+
preferences.getStringSet(PIXEL_5XX_KEYS_SET, mutableSetOf()) ?: mutableSetOf()
53+
}
4154

4255
override fun updateCountPixel(httpErrorPixelName: HttpErrorPixelName) {
4356
val count = preferences.getInt(httpErrorPixelName.appendCountSuffix(), 0)
4457
preferences.edit { putInt(httpErrorPixelName.appendCountSuffix(), count + 1) }
4558
}
4659

60+
override suspend fun update5xxCountPixel(
61+
httpErrorPixelName: HttpErrorPixelName,
62+
statusCode: Int,
63+
) {
64+
// Kill switch
65+
if (!androidBrowserConfig.self().isEnabled() || !androidBrowserConfig.httpError5xxPixel().isEnabled()) {
66+
return
67+
}
68+
69+
val pProVpnConnected = runCatching {
70+
networkProtectionState.isRunning()
71+
}.getOrDefault(false)
72+
73+
val externalVpnConnected = runCatching {
74+
externalVpnDetector.isExternalVpnDetected()
75+
}.getOrDefault(false)
76+
77+
val webViewFullVersion = webViewVersionProvider.getFullVersion()
78+
79+
val pixelPrefKey = "${httpErrorPixelName.pixelName}|$statusCode|$pProVpnConnected|$externalVpnConnected|$webViewFullVersion|_count"
80+
81+
val updatedSet = pixel5xxKeys
82+
updatedSet.add(pixelPrefKey)
83+
val count = preferences.getInt(pixelPrefKey, 0)
84+
preferences.edit {
85+
putInt(pixelPrefKey, count + 1)
86+
putStringSet(PIXEL_5XX_KEYS_SET, updatedSet)
87+
}
88+
}
89+
4790
override fun fireCountPixel(httpErrorPixelName: HttpErrorPixelName) {
4891
val now = Instant.now().toEpochMilli()
4992

@@ -64,6 +107,49 @@ class RealHttpErrorPixels @Inject constructor(
64107
}
65108
}
66109

110+
override fun fire5xxCountPixels() {
111+
// Kill switch
112+
if (!androidBrowserConfig.self().isEnabled() || !androidBrowserConfig.httpError5xxPixel().isEnabled()) {
113+
return
114+
}
115+
116+
val now = Instant.now().toEpochMilli()
117+
val updatedSet = pixel5xxKeys
118+
updatedSet.forEach { pixelKey ->
119+
val count = preferences.getInt(pixelKey, 0)
120+
if (count != 0) {
121+
val timestamp = preferences.getLong("${pixelKey}_timestamp", 0L)
122+
if (timestamp == 0L || now >= timestamp) {
123+
pixelKey.split("|").let { split ->
124+
if (split.size == 6) {
125+
val httpErrorPixelName = HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY
126+
val statusCode = split[1].toInt()
127+
val pProVpnConnected = split[2].toBoolean()
128+
val externalVpnConnected = split[3].toBoolean()
129+
val webViewFullVersion = split[4]
130+
pixel.fire(
131+
httpErrorPixelName,
132+
mapOf(
133+
HttpErrorPixelParameters.HTTP_ERROR_CODE_COUNT to count.toString(),
134+
"error_code" to statusCode.toString(),
135+
"ppro_user" to pProVpnConnected.toString(),
136+
"vpn_user" to externalVpnConnected.toString(),
137+
"webview_version" to webViewFullVersion,
138+
),
139+
)
140+
}
141+
}
142+
.also {
143+
preferences.edit {
144+
putLong("${pixelKey}_timestamp", now.plus(TimeUnit.HOURS.toMillis(WINDOW_INTERVAL_HOURS)))
145+
putInt(pixelKey, 0)
146+
}
147+
}
148+
}
149+
}
150+
}
151+
}
152+
67153
private fun HttpErrorPixelName.appendTimestampSuffix(): String {
68154
return "${this.pixelName}_timestamp"
69155
}
@@ -75,5 +161,6 @@ class RealHttpErrorPixels @Inject constructor(
75161
companion object {
76162
private const val FILENAME = "com.duckduckgo.app.browser.httperrors"
77163
private const val WINDOW_INTERVAL_HOURS = 24L
164+
internal const val PIXEL_5XX_KEYS_SET = "pixel_5xx_keys_set"
78165
}
79166
}

app/src/main/java/com/duckduckgo/app/pixels/remoteconfig/AndroidBrowserConfigFeature.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,12 @@ interface AndroidBrowserConfigFeature {
9999
*/
100100
@Toggle.DefaultValue(false)
101101
fun fireproofedWebLocalStorage(): Toggle
102+
103+
/**
104+
* @return `true` when the remote config has the global "httpError5xxPixel" androidBrowserConfig
105+
* sub-feature flag enabled
106+
* If the remote feature is not present defaults to `false`
107+
*/
108+
@Toggle.DefaultValue(false)
109+
fun httpError5xxPixel(): Toggle
102110
}

app/src/test/java/com/duckduckgo/app/browser/httperrors/HttpErrorDailyReportingWorkerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ internal class HttpErrorDailyReportingWorkerTest {
5353

5454
verify(mockHttpErrorPixels).fireCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_400_DAILY)
5555
verify(mockHttpErrorPixels).fireCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_4XX_DAILY)
56-
verify(mockHttpErrorPixels).fireCountPixel(HttpErrorPixelName.WEBVIEW_RECEIVED_HTTP_ERROR_5XX_DAILY)
56+
verify(mockHttpErrorPixels).fire5xxCountPixels()
5757
assertEquals(result, ListenableWorker.Result.success())
5858
}
5959
}

0 commit comments

Comments
 (0)