Migrate to use send() extension function#7847
Open
marcosholgado wants to merge 1 commit intofeature/marcos/remove_ext_function_metricfrom
Open
Migrate to use send() extension function#7847marcosholgado wants to merge 1 commit intofeature/marcos/remove_ext_function_metricfrom
marcosholgado wants to merge 1 commit intofeature/marcos/remove_ext_function_metricfrom
Conversation
Contributor
Author
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
3 tasks
67ba609 to
ab83106
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Migration to send() skips conversion windows due to short-circuit
- Replaced short-circuiting
anywith full iteration inRealMetricsPixelSender.sendso every pixel definition is processed while still returning whether any definition fired, and added regression tests for overlapping conversion windows.
- Replaced short-circuiting
Or push these changes by commenting:
@cursor push 1561d0b17c
Preview (1561d0b17c)
diff --git a/feature-toggles/feature-toggles-impl/src/main/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSender.kt b/feature-toggles/feature-toggles-impl/src/main/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSender.kt
--- a/feature-toggles/feature-toggles-impl/src/main/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSender.kt
+++ b/feature-toggles/feature-toggles-impl/src/main/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSender.kt
@@ -50,13 +50,16 @@
val definitions = metricsPixel.getPixelDefinitions()
if (definitions.isEmpty()) return false
return withContext(dispatcherProvider.io()) {
- definitions.any { definition ->
- when (metricsPixel.type) {
+ var sent = false
+ definitions.forEach { definition ->
+ val definitionSent = when (metricsPixel.type) {
MetricType.NORMAL -> sendNormal(definition)
MetricType.COUNT_WHEN_IN_WINDOW -> sendCount(definition, metricsPixel.value.toInt())
MetricType.COUNT_ALWAYS -> false // TODO: not implemented yet
}
+ sent = sent || definitionSent
}
+ sent
}
}
diff --git a/feature-toggles/feature-toggles-impl/src/test/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSenderTest.kt b/feature-toggles/feature-toggles-impl/src/test/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSenderTest.kt
--- a/feature-toggles/feature-toggles-impl/src/test/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSenderTest.kt
+++ b/feature-toggles/feature-toggles-impl/src/test/java/com/duckduckgo/feature/toggles/impl/RealMetricsPixelSenderTest.kt
@@ -93,6 +93,21 @@
}
@Test
+ fun `NORMAL - fires all matching conversion windows in a single send`() = runTest {
+ val pixel = metricsPixel(
+ type = MetricType.NORMAL,
+ conversionWindow = listOf(
+ ConversionWindow(lowerWindow = 0, upperWindow = 1),
+ ConversionWindow(lowerWindow = 0, upperWindow = 2),
+ ),
+ )
+
+ sender.send(pixel)
+
+ assertEquals(2, fakePixel.firedPixels.size)
+ }
+
+ @Test
fun `NORMAL - returns false when no cohort assigned`() = runTest {
testFeature.experimentFooFeature().setRawStoredState(
State(remoteEnableState = true, enable = true, assignedCohort = null),
@@ -140,6 +155,22 @@
}
@Test
+ fun `COUNT_WHEN_IN_WINDOW - fires all matching conversion windows when threshold reached`() = runTest {
+ val pixel = metricsPixel(
+ type = MetricType.COUNT_WHEN_IN_WINDOW,
+ value = "2",
+ conversionWindow = listOf(
+ ConversionWindow(lowerWindow = 0, upperWindow = 1),
+ ConversionWindow(lowerWindow = 0, upperWindow = 2),
+ ),
+ )
+
+ repeat(2) { sender.send(pixel) }
+
+ assertEquals(2, fakePixel.firedPixels.size)
+ }
+
+ @Test
fun `COUNT_WHEN_IN_WINDOW - pixel not fired again after threshold`() = runTest {
val pixel = metricsPixel(type = MetricType.COUNT_WHEN_IN_WINDOW, value = "3", lowerWindow = 0, upperWindow = 1)
repeat(5) { sender.send(pixel) }
@@ -206,11 +237,12 @@
value: String = "1",
lowerWindow: Int = 0,
upperWindow: Int = 1,
+ conversionWindow: List<ConversionWindow> = listOf(ConversionWindow(lowerWindow, upperWindow)),
) = MetricsPixel(
metric = "test_metric",
value = value,
toggle = testFeature.experimentFooFeature(),
- conversionWindow = listOf(ConversionWindow(lowerWindow, upperWindow)),
+ conversionWindow = conversionWindow,
type = type,
)
}
.../main/java/com/duckduckgo/feature/toggles/impl/metrics/RetentionMetricsAtbLifecyclePlugin.kt
Show resolved
Hide resolved
ab83106 to
439483e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Task/Issue URL: https://app.asana.com/1/137249556945/project/1208889145294658/task/1213495482913540?focus=true
Description
This PR introduces a
send()extension function onMetricsPixelas the single public API for firing experiment metric pixels, and migrates all existing callers to use it. It does also delete the metrics pixel interceptor.Also changed
send()to not return a Boolean since it can send multiple pixels and could get confusing plus we don't really know if the pixel was sent or not.The interceptor logic is now simplified in the send() implementation. Before we were intercepting pixels and checking the metric we were trying to send was actually defined (to avoid people just firing whatever they wanted). This is now not possible because the only way is to fire through the send() extension function.
Steps to test this PR
To trigger the experiment, add the following temporary code and point the app at the remote config below.
Remote config:
https://gist.githubusercontent.com/marcosholgado/cfcc2a9941fa667c68bbf85cc8ac3ac9/raw/6311d66e36960c0a7b8f6db3cd8c51cac7ee7563/extension.jsonTrigger code (add temporarily to any impl module, for example in
ContentScopeExperimentsFeature):@ContributesRemoteFeature( scope = AppScope::class, featureName = "experimentTest", ) interface experimentTest { @Toggle.DefaultValue(DefaultFeatureValue.TRUE) fun self(): Toggle @Toggle.DefaultValue(DefaultFeatureValue.TRUE) fun experimentTestAA(): Toggle enum class Cohorts(override val cohortName: String) : CohortName { CONTROL("control"), TREATMENT("treatment"), } } @ContributesMultibinding( scope = AppScope::class, boundType = MainProcessLifecycleObserver::class, ) class TestObserver @Inject constructor( @AppCoroutineScope val appCoroutineScope: CoroutineScope, private val experimentTest: experimentTest, ) : MainProcessLifecycleObserver { override fun onStart(owner: LifecycleOwner) { super.onStart(owner) appCoroutineScope.launch { experimentTest.experimentTestAA().enroll() } } }Note
Medium Risk
Changes the experiment metrics pixel sending path by removing the network interceptor and routing all metric pixels through the new
send()implementation, which could impact experiment analytics if conversion-window or dedupe behavior differs. Call-site updates are broad but mechanically straightforward and covered by updated tests.Overview
Unifies experiment metric pixel firing behind the public
MetricsPixel.send()API and migrates existing callers (AppTPDeviceShieldPixels, refresh-pattern pixels, and blocklist privacy toggle) from manualgetPixelDefinitions()iteration to a singlesend()call.Removes the OkHttp
MetricPixelInterceptor(and its tests) and shifts conversion-window/dedup/count-threshold handling into thefeature-toggles-implsender; metric definitions for retention now explicitly useMetricType.COUNT_WHEN_IN_WINDOW.Updates tests to assert metrics were sent via
FakeMetricsPixelExtensionrather than verifying low-levelPixel.firecalls, and tightens the lint rule to only ban direct imports ofMetricsPixelExtension/MetricsPixelExtensionProviderby type name.Written by Cursor Bugbot for commit ab83106. This will update automatically on new commits. Configure here.