Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ dependencies {
implementation project(':survey-api')
implementation project(':survey-impl')

implementation project(':attributed-metrics-api')
implementation project(':attributed-metrics-impl')

implementation project(':breakage-reporting-impl')

implementation project(':dax-prompts-api')
Expand Down
36 changes: 36 additions & 0 deletions attributed-metrics/attributed-metrics-api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id 'java-library'
id 'kotlin'
}

apply from: "$rootProject.projectDir/code-formatting.gradle"

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlin {
jvmToolchain(17)
}

dependencies {
implementation Kotlin.stdlib.jdk7
implementation KotlinX.coroutines.core
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.attributed.metrics.api

/**
* Client for collecting and emitting attributed metrics.
*/
interface AttributedMetricClient {
/**
* Stores an event occurrence for later analysis.
* Does nothing if the client is not active.
*
* @param eventName Name of the event to collect
*/
fun collectEvent(eventName: String)

/**
* Calculates statistics for a specific event over a time period.
* Returns zero stats if the client is not active.
*
* @param eventName Name of the event to analyze
* @param days Number of days to look back
* @return Statistics about the event's occurrences
*/
suspend fun getEventStats(
eventName: String,
days: Int,
): EventStats

/**
* Emits a metric with its parameters if the client is active.
* Does nothing if the client is not active.
*
* @param metric The metric to emit
*/
fun emitMetric(metric: AttributedMetric)
}

/**
* Statistics about collected events over a time period.
*
* @property daysWithEvents Number of days that had at least one event
* @property rollingAverage Average number of events per day over the period
* @property totalEvents Total number of events in the period
*/
data class EventStats(
val daysWithEvents: Int,
val rollingAverage: Double,
val totalEvents: Int,
)

/**
* Interface for defining an attributed metric.
* Each metric implementation should provide its name and parameters.
*/
interface AttributedMetric {
/**
* @return The name used to identify this metric
*/
fun getPixelName(): String

/**
* @return Parameters to be included with this metric
*/
suspend fun getMetricParameters(): Map<String, String>
}
91 changes: 91 additions & 0 deletions attributed-metrics/attributed-metrics-impl/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id 'com.android.library'
id 'kotlin-android'
id 'com.google.devtools.ksp'
id 'com.squareup.anvil'
}

apply from: "$rootProject.projectDir/gradle/android-library.gradle"

dependencies {
anvil project(path: ':anvil-compiler')

implementation project(path: ':anvil-annotations')
implementation project(path: ':attributed-metrics-api')
implementation project(path: ':common-utils')
implementation project(path: ':di')
implementation project(path: ':app-build-config-api')
implementation project(path: ':statistics-api')

implementation KotlinX.coroutines.core
implementation KotlinX.coroutines.android

implementation Google.dagger

// DataStore
api AndroidX.dataStore.preferences

// Room
implementation AndroidX.room.ktx
ksp AndroidX.room.compiler

implementation "com.squareup.logcat:logcat:_"

implementation AndroidX.core.ktx

testImplementation Testing.junit4
testImplementation "org.mockito.kotlin:mockito-kotlin:_"
testImplementation "androidx.lifecycle:lifecycle-runtime-testing:_"
testImplementation project(path: ':common-test')
testImplementation project(':data-store-test')
testImplementation project(':feature-toggles-test')
testImplementation CashApp.turbine
testImplementation Testing.robolectric
testImplementation(KotlinX.coroutines.test) {
// https://github.com/Kotlin/kotlinx.coroutines/issues/2023
// conflicts with mockito due to direct inclusion of byte buddy
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
}
testImplementation AndroidX.test.ext.junit
testImplementation AndroidX.archCore.testing
testImplementation AndroidX.room.testing
testImplementation AndroidX.room.rxJava2

androidTestImplementation AndroidX.test.runner
androidTestImplementation AndroidX.test.rules

coreLibraryDesugaring Android.tools.desugarJdkLibs
}

android {
anvil {
generateDaggerFactories = true // default is false
}
lintOptions {
baseline file("lint-baseline.xml")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't think we need this since there are no linting issues

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

abortOnError = !project.hasProperty("abortOnError") || project.property("abortOnError") != "false"
}
namespace 'com.duckduckgo.app.attributed.metrics'
compileOptions {
coreLibraryDesugaringEnabled = true
}
buildFeatures {
buildConfig = true
}
}
4 changes: 4 additions & 0 deletions attributed-metrics/attributed-metrics-impl/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think we need the baseline file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was needed, lint failed and I had to create the baseline.

<issues format="6" by="lint 8.8.2" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.3)" variant="all" version="8.8.2">

</issues>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2025 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.attributed.metrics

import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.DefaultFeatureValue

@ContributesRemoteFeature(
scope = AppScope::class,
featureName = "attributedMetrics",
)
interface AttributedMetricsConfigFeature {
@Toggle.DefaultValue(DefaultFeatureValue.INTERNAL)
fun self(): Toggle
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.attributed.metrics

internal interface AttributedMetricsState {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda confused by this, you also have another AttributedMetricsState but with isActive... maybe the fact that the interface name is the same? Also not sure if this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be removed, leftover when refactoring some code. It got replaced by the other one with same name.

fun isEnabled(): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.attributed.metrics.di

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dagger.SingleInstanceIn
import javax.inject.Qualifier

@Module
@ContributesTo(AppScope::class)
object AttributedMetricsDataStoreModule {
@Provides
@SingleInstanceIn(AppScope::class)
@AttributedMetrics
fun provideDataStore(context: Context): DataStore<Preferences> =
PreferenceDataStoreFactory.create(
produceFile = { context.preferencesDataStoreFile("attributed_metrics_v1") },
)
}

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AttributedMetrics
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.attributed.metrics.di

import android.content.Context
import androidx.room.Room
import com.duckduckgo.app.attributed.metrics.store.AttributedMetricsDatabase
import com.duckduckgo.app.attributed.metrics.store.EventDao
import com.duckduckgo.di.scopes.AppScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dagger.SingleInstanceIn

@Module
@ContributesTo(AppScope::class)
class AttributedMetricsDatabaseModule {
@Provides
@SingleInstanceIn(AppScope::class)
fun provideAttributedMetricsDatabase(context: Context): AttributedMetricsDatabase =
Room
.databaseBuilder(
context = context,
klass = AttributedMetricsDatabase::class.java,
name = "attributed_metrics.db",
).fallbackToDestructiveMigration()
.build()

@Provides
@SingleInstanceIn(AppScope::class)
fun provideEventDao(db: AttributedMetricsDatabase): EventDao = db.eventDao()
}
Loading
Loading