Skip to content

Commit 3c6096a

Browse files
committed
remove form factor, and add default attributed metric params
1 parent dc606a2 commit 3c6096a

File tree

26 files changed

+540
-138
lines changed

26 files changed

+540
-138
lines changed

ad-click/ad-click-impl/src/main/java/com/duckduckgo/adclick/impl/metrics/AdClickAttributedMetric.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class RealAdClickAttributedMetric @Inject constructor(
6464

6565
companion object {
6666
private const val EVENT_NAME = "ad_click"
67-
private const val PIXEL_NAME = "user_average_ad_clicks_past_week"
67+
private const val PIXEL_NAME = "attributed_metric_average_ad_clicks_past_week"
6868
private const val FEATURE_TOGGLE_NAME = "adClickCountAvg"
6969
private const val FEATURE_EMIT_TOGGLE_NAME = "canEmitAdClickCountAvg"
7070
private const val DAYS_WINDOW = 7
@@ -108,6 +108,7 @@ class RealAdClickAttributedMetric @Inject constructor(
108108
val stats = getEventStats()
109109
val params = mutableMapOf(
110110
"count" to getBucketValue(stats.rollingAverage.roundToInt()).toString(),
111+
"version" to bucketConfig.await().version.toString(),
111112
)
112113
if (!hasCompleteDataWindow()) {
113114
params["dayAverage"] = daysSinceInstalled().toString()

ad-click/ad-click-impl/src/test/java/com/duckduckgo/adclick/impl/metrics/RealAdClickAttributedMetricTest.kt

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class RealAdClickAttributedMetricTest {
6363
)
6464
whenever(attributedMetricConfig.getBucketConfiguration()).thenReturn(
6565
mapOf(
66-
"user_average_ad_clicks_past_week" to MetricBucket(
66+
"attributed_metric_average_ad_clicks_past_week" to MetricBucket(
6767
buckets = listOf(2, 5),
6868
version = 0,
6969
),
@@ -79,7 +79,7 @@ class RealAdClickAttributedMetricTest {
7979
}
8080

8181
@Test fun whenPixelNameRequestedThenReturnCorrectName() {
82-
assertEquals("user_average_ad_clicks_past_week", testee.getPixelName())
82+
assertEquals("attributed_metric_average_ad_clicks_past_week", testee.getPixelName())
8383
}
8484

8585
@Test fun whenAdClickAndDaysInstalledIsZeroThenDoNotEmitMetric() = runTest {
@@ -222,12 +222,12 @@ class RealAdClickAttributedMetricTest {
222222
),
223223
)
224224

225-
val params = testee.getMetricParameters()
225+
val count = testee.getMetricParameters()["count"]
226226

227227
assertEquals(
228228
"For $clicksAvg clicks, should return bucket $expectedBucket",
229-
mapOf("count" to expectedBucket.toString()),
230-
params,
229+
expectedBucket.toString(),
230+
count,
231231
)
232232
}
233233
}
@@ -255,6 +255,21 @@ class RealAdClickAttributedMetricTest {
255255
}
256256
}
257257

258+
@Test fun whenGetMetricParametersThenReturnVersion() = runTest {
259+
givenDaysSinceInstalled(7)
260+
whenever(attributedMetricClient.getEventStats("ad_click", 7)).thenReturn(
261+
EventStats(
262+
daysWithEvents = 1,
263+
rollingAverage = 1.0,
264+
totalEvents = 1,
265+
),
266+
)
267+
268+
val version = testee.getMetricParameters()["version"]
269+
270+
assertEquals("0", version)
271+
}
272+
258273
private fun givenDaysSinceInstalled(days: Int) {
259274
val etZone = ZoneId.of("America/New_York")
260275
val now = Instant.now()

app/src/main/java/com/duckduckgo/app/referral/AppReferrerDataStore.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class AppReferenceSharePreferences @Inject constructor(
5757
}
5858
}
5959

60+
override fun getOriginAttributeCampaign(): String? = utmOriginAttributeCampaign
61+
6062
override var campaignSuffix: String?
6163
get() = preferences.getString(KEY_CAMPAIGN_SUFFIX, null)
6264
set(value) = preferences.edit(true) { putString(KEY_CAMPAIGN_SUFFIX, value) }

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/impl/RealAttributedMetricClient.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ package com.duckduckgo.app.attributed.metrics.impl
1919
import com.duckduckgo.app.attributed.metrics.api.AttributedMetric
2020
import com.duckduckgo.app.attributed.metrics.api.AttributedMetricClient
2121
import com.duckduckgo.app.attributed.metrics.api.EventStats
22+
import com.duckduckgo.app.attributed.metrics.store.AttributedMetricsDateUtils
2223
import com.duckduckgo.app.attributed.metrics.store.EventRepository
2324
import com.duckduckgo.app.di.AppCoroutineScope
2425
import com.duckduckgo.app.statistics.pixels.Pixel
2526
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique
27+
import com.duckduckgo.browser.api.install.AppInstall
28+
import com.duckduckgo.browser.api.referrer.AppReferrer
2629
import com.duckduckgo.common.utils.DispatcherProvider
2730
import com.duckduckgo.di.scopes.AppScope
2831
import com.squareup.anvil.annotations.ContributesBinding
@@ -41,6 +44,9 @@ class RealAttributedMetricClient @Inject constructor(
4144
private val eventRepository: EventRepository,
4245
private val pixel: Pixel,
4346
private val metricsState: AttributedMetricsState,
47+
private val appReferrer: AppReferrer,
48+
private val dateUtils: AttributedMetricsDateUtils,
49+
private val appInstall: AppInstall,
4450
) : AttributedMetricClient {
4551

4652
override fun collectEvent(eventName: String) {
@@ -77,7 +83,6 @@ class RealAttributedMetricClient @Inject constructor(
7783
}
7884
}
7985

80-
// TODO: Pending adding default attributed metrics and removing default prefix from pixel names
8186
override fun emitMetric(metric: AttributedMetric) {
8287
appCoroutineScope.launch(dispatcherProvider.io()) {
8388
if (!metricsState.isActive() || !metricsState.canEmitMetrics()) {
@@ -91,11 +96,23 @@ class RealAttributedMetricClient @Inject constructor(
9196
val params = metric.getMetricParameters()
9297
val tag = metric.getTag()
9398
val pixelTag = "${pixelName}_$tag"
94-
pixel.fire(pixelName = pixelName, parameters = params, type = Unique(pixelTag)).also {
99+
100+
val origin = appReferrer.getOriginAttributeCampaign()
101+
val paramsMutableMap = params.toMutableMap()
102+
if (!origin.isNullOrBlank()) {
103+
paramsMutableMap["origin"] = origin
104+
} else {
105+
paramsMutableMap["install_date"] = getInstallDate()
106+
}
107+
pixel.fire(pixelName = pixelName, parameters = paramsMutableMap, type = Unique(pixelTag)).also {
95108
logcat(tag = "AttributedMetrics") {
96-
"Fired pixel $pixelName with params $params"
109+
"Fired pixel $pixelName with params $paramsMutableMap"
97110
}
98111
}
99112
}
100113
}
114+
115+
private fun getInstallDate(): String {
116+
return dateUtils.getDateFromTimestamp(appInstall.getInstallationTimestamp())
117+
}
101118
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.attributed.metrics.pixels
18+
19+
import com.duckduckgo.common.utils.device.DeviceInfo
20+
import com.duckduckgo.common.utils.plugins.pixel.PixelInterceptorPlugin
21+
import com.duckduckgo.di.scopes.AppScope
22+
import com.squareup.anvil.annotations.ContributesMultibinding
23+
import logcat.logcat
24+
import okhttp3.HttpUrl.Companion.toHttpUrl
25+
import okhttp3.Interceptor
26+
import okhttp3.Response
27+
import javax.inject.Inject
28+
29+
@ContributesMultibinding(
30+
scope = AppScope::class,
31+
boundType = PixelInterceptorPlugin::class,
32+
)
33+
class AttributedMetricPixelInterceptor @Inject constructor() : Interceptor, PixelInterceptorPlugin {
34+
35+
override fun intercept(chain: Interceptor.Chain): Response {
36+
val request = chain.request().newBuilder()
37+
var url = chain.request().url
38+
val pixel = chain.request().url.pathSegments.last()
39+
if (pixel.startsWith(ATTRIBUTED_METRICS_PIXEL_PREFIX)) {
40+
url = url.toUrl().toString().replace("android_${DeviceInfo.FormFactor.PHONE.description}", "android").toHttpUrl()
41+
logcat(tag = "AttributedMetrics") {
42+
"Pixel renamed to: $url"
43+
}
44+
}
45+
return chain.proceed(request.url(url).build())
46+
}
47+
48+
override fun getInterceptor() = this
49+
50+
companion object {
51+
const val ATTRIBUTED_METRICS_PIXEL_PREFIX = "attributed_metric"
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.attributed.metrics.pixels
18+
19+
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin
20+
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin.PixelParameter
21+
import com.duckduckgo.di.scopes.AppScope
22+
import com.squareup.anvil.annotations.ContributesMultibinding
23+
24+
@ContributesMultibinding(
25+
scope = AppScope::class,
26+
boundType = PixelParamRemovalPlugin::class,
27+
)
28+
object AttributedMetricPixelRemovalInterceptor : PixelParamRemovalPlugin {
29+
override fun names(): List<Pair<String, Set<PixelParameter>>> {
30+
return listOf(
31+
ATTRIBUTED_METRICS_PIXEL_PREFIX to PixelParameter.removeAll(),
32+
)
33+
}
34+
35+
private const val ATTRIBUTED_METRICS_PIXEL_PREFIX = "attributed_metric"
36+
}

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/retention/RetentionMonthAttributedMetric.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class RetentionMonthAttributedMetric @Inject constructor(
4949
) : AttributedMetric, AtbLifecyclePlugin {
5050

5151
companion object {
52-
private const val PIXEL_NAME_FIRST_MONTH = "user_retention_month"
52+
private const val PIXEL_NAME_FIRST_MONTH = "attributed_metric_retention_month"
5353
private const val DAYS_IN_4_WEEKS = 28 // we consider 1 month after 4 weeks
5454
private const val MONTH_DAY_THRESHOLD = DAYS_IN_4_WEEKS + 1
5555
private const val START_MONTH_THRESHOLD = 2
@@ -103,7 +103,11 @@ class RetentionMonthAttributedMetric @Inject constructor(
103103
val month = getMonthSinceInstall()
104104
if (month < START_MONTH_THRESHOLD) return emptyMap()
105105

106-
return mutableMapOf("count" to bucketMonth(month).toString())
106+
val params = mutableMapOf(
107+
"count" to bucketMonth(month).toString(),
108+
"version" to bucketConfig.await().version.toString(),
109+
)
110+
return params
107111
}
108112

109113
override suspend fun getTag(): String {

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/retention/RetentionWeekAttributedMetric.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class RetentionWeekAttributedMetric @Inject constructor(
4949
) : AttributedMetric, AtbLifecyclePlugin {
5050

5151
companion object {
52-
private const val PIXEL_NAME_FIRST_WEEK = "user_retention_week"
52+
private const val PIXEL_NAME_FIRST_WEEK = "attributed_metric_retention_week"
5353
private const val FEATURE_TOGGLE_NAME = "retention"
5454
private const val FEATURE_EMIT_TOGGLE_NAME = "canEmitRetention"
5555
}
@@ -98,7 +98,12 @@ class RetentionWeekAttributedMetric @Inject constructor(
9898
override suspend fun getMetricParameters(): Map<String, String> {
9999
val week = getWeekSinceInstall()
100100
if (week == -1) return emptyMap()
101-
return mutableMapOf("count" to bucketValue(getWeekSinceInstall()).toString())
101+
102+
val params = mutableMapOf(
103+
"count" to bucketValue(getWeekSinceInstall()).toString(),
104+
"version" to bucketConfig.await().version.toString(),
105+
)
106+
return params
102107
}
103108

104109
override suspend fun getTag(): String {

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/search/SearchAttributedMetric.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ class SearchAttributedMetric @Inject constructor(
6161

6262
companion object {
6363
private const val EVENT_NAME = "ddg_search"
64-
private const val FIRST_MONTH_PIXEL = "user_average_searches_past_week_first_month"
65-
private const val PAST_WEEK_PIXEL_NAME = "user_average_searches_past_week"
64+
private const val FIRST_MONTH_PIXEL = "attributed_metric_average_searches_past_week_first_month"
65+
private const val PAST_WEEK_PIXEL_NAME = "attributed_metric_average_searches_past_week"
6666
private const val DAYS_WINDOW = 7
6767
private const val FIRST_MONTH_DAY_THRESHOLD = 28 // we consider 1 month after 4 weeks
6868
private const val FEATURE_TOGGLE_NAME = "searchCountAvg"
@@ -126,6 +126,7 @@ class SearchAttributedMetric @Inject constructor(
126126
val stats = getEventStats()
127127
val params = mutableMapOf(
128128
"count" to getBucketValue(stats.rollingAverage.roundToInt()).toString(),
129+
"version" to getBucketConfig().version.toString(),
129130
)
130131
if (!hasCompleteDataWindow()) {
131132
params["dayAverage"] = daysSinceInstalled().toString()
@@ -141,10 +142,7 @@ class SearchAttributedMetric @Inject constructor(
141142
}
142143

143144
private suspend fun getBucketValue(searches: Int): Int {
144-
val buckets = when (daysSinceInstalled()) {
145-
in 0..FIRST_MONTH_DAY_THRESHOLD -> bucketConfigFirstMonth.await().buckets
146-
else -> bucketConfigPastWeek.await().buckets
147-
}
145+
val buckets = getBucketConfig().buckets
148146
return buckets.indexOfFirst { bucket -> searches <= bucket }.let { index ->
149147
if (index == -1) buckets.size else index
150148
}
@@ -177,6 +175,11 @@ class SearchAttributedMetric @Inject constructor(
177175
return stats
178176
}
179177

178+
private suspend fun getBucketConfig() = when (daysSinceInstalled()) {
179+
in 0..FIRST_MONTH_DAY_THRESHOLD -> bucketConfigFirstMonth.await()
180+
else -> bucketConfigPastWeek.await()
181+
}
182+
180183
private fun hasCompleteDataWindow(): Boolean {
181184
val daysSinceInstalled = daysSinceInstalled()
182185
return daysSinceInstalled >= DAYS_WINDOW

attributed-metrics/attributed-metrics-impl/src/main/java/com/duckduckgo/app/attributed/metrics/search/SearchDaysAttributedMetric.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class SearchDaysAttributedMetric @Inject constructor(
5959

6060
companion object {
6161
private const val EVENT_NAME = "ddg_search_days"
62-
private const val PIXEL_NAME = "user_active_past_week"
62+
private const val PIXEL_NAME = "attributed_metric_active_past_week"
6363
private const val DAYS_WINDOW = 7
6464
private const val FEATURE_TOGGLE_NAME = "searchDaysAvg"
6565
private const val FEATURE_EMIT_TOGGLE_NAME = "canEmitSearchDaysAvg"
@@ -124,6 +124,7 @@ class SearchDaysAttributedMetric @Inject constructor(
124124
val stats = attributedMetricClient.getEventStats(EVENT_NAME, DAYS_WINDOW)
125125
val params = mutableMapOf(
126126
"days" to getBucketValue(stats.daysWithEvents).toString(),
127+
"version" to bucketConfiguration.await().version.toString(),
127128
)
128129
if (!hasCompleteDataWindow) {
129130
params["daysSinceInstalled"] = daysSinceInstalled.toString()

0 commit comments

Comments
 (0)