diff --git a/.gitignore b/.gitignore index e16803b4..15e7df3b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ local.properties *.iml # generated files -/build +build/ /captures .kotlin/ diff --git a/analytics/.gitignore b/analytics/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/analytics/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/analytics/build.gradle.kts b/analytics/build.gradle.kts new file mode 100644 index 00000000..0950dec3 --- /dev/null +++ b/analytics/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + alias(libs.plugins.gravatar.android.library) +} + +android { + namespace = "com.gravatar.analytics" +} + +dependencies { + + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + implementation(libs.automattic.tracks) + + testImplementation(libs.junit) + testImplementation(libs.koin.test.junit4) + testImplementation(libs.mockk.android) +} \ No newline at end of file diff --git a/analytics/consumer-rules.pro b/analytics/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/analytics/proguard-rules.pro b/analytics/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/analytics/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/analytics/src/main/kotlin/com/gravatar/analytics/Event.kt b/analytics/src/main/kotlin/com/gravatar/analytics/Event.kt new file mode 100644 index 00000000..a0905d98 --- /dev/null +++ b/analytics/src/main/kotlin/com/gravatar/analytics/Event.kt @@ -0,0 +1,5 @@ +package com.gravatar.analytics + +interface Event { + val name: String +} diff --git a/analytics/src/main/kotlin/com/gravatar/analytics/Tracker.kt b/analytics/src/main/kotlin/com/gravatar/analytics/Tracker.kt new file mode 100644 index 00000000..fc9394cc --- /dev/null +++ b/analytics/src/main/kotlin/com/gravatar/analytics/Tracker.kt @@ -0,0 +1,7 @@ +package com.gravatar.analytics + +abstract class Tracker { + abstract var userId: String? + abstract fun trackEvent(event: Event) + abstract fun flush() +} diff --git a/analytics/src/main/kotlin/com/gravatar/analytics/di/AnalyticsModule.kt b/analytics/src/main/kotlin/com/gravatar/analytics/di/AnalyticsModule.kt new file mode 100644 index 00000000..46439b8a --- /dev/null +++ b/analytics/src/main/kotlin/com/gravatar/analytics/di/AnalyticsModule.kt @@ -0,0 +1,13 @@ +package com.gravatar.analytics.di + +import com.automattic.android.tracks.TracksClient +import com.gravatar.analytics.Tracker +import com.gravatar.analytics.tracks.TracksTracker +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val analyticsModule = module { + single { TracksClient.getClient(get()) } + singleOf(::TracksTracker) { bind() } +} diff --git a/analytics/src/main/kotlin/com/gravatar/analytics/tracks/TracksTracker.kt b/analytics/src/main/kotlin/com/gravatar/analytics/tracks/TracksTracker.kt new file mode 100644 index 00000000..dfd86320 --- /dev/null +++ b/analytics/src/main/kotlin/com/gravatar/analytics/tracks/TracksTracker.kt @@ -0,0 +1,27 @@ +package com.gravatar.analytics.tracks + +import com.automattic.android.tracks.TracksClient +import com.gravatar.analytics.Event +import com.gravatar.analytics.Tracker +import java.util.UUID + +internal class TracksTracker(private val tracksClient: TracksClient) : Tracker() { + override var userId: String? = null + private val anonId: String = generateNewAnonID() + + override fun trackEvent(event: Event) { + val userType = userId?.let { + TracksClient.NosaraUserType.WPCOM + } ?: TracksClient.NosaraUserType.ANON + tracksClient.track(event.name, userId ?: anonId, userType) + } + + override fun flush() { + tracksClient.flush() + } +} + +private fun generateNewAnonID(): String { + // Generate a new UUID and return it as a string. + return UUID.randomUUID().toString() +} diff --git a/analytics/src/test/kotlin/com/gravatar/analytics/di/AnalyticsModuleTest.kt b/analytics/src/test/kotlin/com/gravatar/analytics/di/AnalyticsModuleTest.kt new file mode 100644 index 00000000..f4090449 --- /dev/null +++ b/analytics/src/test/kotlin/com/gravatar/analytics/di/AnalyticsModuleTest.kt @@ -0,0 +1,23 @@ +package com.gravatar.analytics.di + +import android.content.Context +import com.gravatar.analytics.tracks.TracksTracker +import org.junit.Test +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.test.KoinTest +import org.koin.test.verify.definition +import org.koin.test.verify.injectedParameters +import org.koin.test.verify.verify + +class AnalyticsModuleTest : KoinTest { + + @OptIn(KoinExperimentalAPI::class) + @Test + fun checkAllModules() { + analyticsModule.verify( + injections = injectedParameters( + definition(Context::class) + ) + ) + } +} diff --git a/analytics/src/test/kotlin/com/gravatar/analytics/tracks/TrackTrackerTest.kt b/analytics/src/test/kotlin/com/gravatar/analytics/tracks/TrackTrackerTest.kt new file mode 100644 index 00000000..82e4bdcf --- /dev/null +++ b/analytics/src/test/kotlin/com/gravatar/analytics/tracks/TrackTrackerTest.kt @@ -0,0 +1,53 @@ +package com.gravatar.analytics.tracks + +import com.automattic.android.tracks.TracksClient +import com.gravatar.analytics.Event +import io.mockk.mockk +import io.mockk.verify +import io.mockk.verifySequence +import org.junit.Before +import org.junit.Test + +class TrackTrackerTest { + + private lateinit var tracker: TracksTracker + private lateinit var mockClient: TracksClient + + @Before + fun setUp() { + mockClient = mockk(relaxed = true) + tracker = TracksTracker(mockClient) + } + + @Test + fun `when trackEvent is invoked without userId then call track on client with ANON`() { + val event = object : Event { + override val name: String = "test_event" + } + + tracker.trackEvent(event) + + verify { mockClient.track(event.name, any(), TracksClient.NosaraUserType.ANON) } + } + + @Test + fun `when trackEvent is invoked with userId then call track on client with GRAVATAR`() { + val event = object : Event { + override val name: String = "test_event_with_user" + } + tracker.userId = "someUserId" + + tracker.trackEvent(event) + + verify { mockClient.track(event.name, "someUserId", TracksClient.NosaraUserType.WPCOM) } + } + + @Test + fun `when flush is invoked then call flush on client`() { + tracker.flush() + + verifySequence { + mockClient.flush() + } + } +} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2f280611..7703c41b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,6 +10,8 @@ android { dependencies { implementation(project(":homeUi")) implementation(project(":loginUi")) + implementation(project(":analytics")) + implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ae3b5445..b9e6b8c9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + +