Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Fullstory Analytics SDK Implementation #347

Merged
merged 11 commits into from
Jul 9, 2024
16 changes: 16 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ def appId = config.getOrDefault("APPLICATION_ID", "org.openedx.app")
def themeDirectory = config.getOrDefault("THEME_DIRECTORY", "openedx")
def firebaseConfig = config.get('FIREBASE')
def firebaseEnabled = firebaseConfig?.getOrDefault('ENABLED', false)
def fullstoryConfig = config.get("FULLSTORY")
def fullstoryEnabled = fullstoryConfig?.getOrDefault('ENABLED', false)

apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
apply plugin: 'fullstory'

if (firebaseEnabled) {
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
Expand All @@ -25,6 +29,18 @@ if (firebaseEnabled) {
preBuild.dependsOn(removeGoogleServicesJson)
}

if (fullstoryEnabled) {
def fullstoryOrgId = fullstoryConfig?.get("ORG_ID")

fullstory {
org fullstoryOrgId
composeEnabled true
composeSelectorVersion 4
enabledVariants 'debug|release'
logcatLevel 'error'
}
}

android {
compileSdk 34

Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/org/openedx/app/AnalyticsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.openedx.app
import android.content.Context
import org.openedx.app.analytics.Analytics
import org.openedx.app.analytics.FirebaseAnalytics
import org.openedx.app.analytics.FullstoryAnalytics
import org.openedx.app.analytics.SegmentAnalytics
import org.openedx.auth.presentation.AuthAnalytics
import org.openedx.core.config.Config
Expand All @@ -29,10 +30,15 @@ class AnalyticsManager(
if (config.getFirebaseConfig().enabled) {
addAnalyticsTracker(FirebaseAnalytics(context = context))
}

val segmentConfig = config.getSegmentConfig()
if (segmentConfig.enabled && segmentConfig.segmentWriteKey.isNotBlank()) {
addAnalyticsTracker(SegmentAnalytics(context = context, config = config))
}

if (config.getFullstoryConfig().isEnabled) {
addAnalyticsTracker(FullstoryAnalytics())
}
}

private fun addAnalyticsTracker(analytic: Analytics) {
Expand All @@ -45,6 +51,12 @@ class AnalyticsManager(
}
}

override fun logScreenEvent(screenName: String, params: Map<String, Any?>) {
services.forEach { analytics ->
analytics.logScreenEvent(screenName, params)
}
}

override fun logEvent(event: String, params: Map<String, Any?>) {
services.forEach { analytics ->
analytics.logEvent(event, params)
Expand Down
9 changes: 1 addition & 8 deletions app/src/main/java/org/openedx/app/AppAnalytics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ interface AppAnalytics {
fun logoutEvent(force: Boolean)
fun setUserIdForSession(userId: Long)
fun logEvent(event: String, params: Map<String, Any?>)
fun logScreenEvent(screenName: String, params: Map<String, Any?>)
}

enum class AppAnalyticsEvent(val eventName: String, val biValue: String) {
Expand All @@ -15,14 +16,6 @@ enum class AppAnalyticsEvent(val eventName: String, val biValue: String) {
"MainDashboard:Discover",
"edx.bi.app.main_dashboard.discover"
),
MY_COURSES(
"MainDashboard:My Courses",
"edx.bi.app.main_dashboard.my_course"
),
MY_PROGRAMS(
"MainDashboard:My Programs",
"edx.bi.app.main_dashboard.my_program"
),
PROFILE(
"MainDashboard:Profile",
"edx.bi.app.main_dashboard.profile"
Expand Down
1 change: 0 additions & 1 deletion app/src/main/java/org/openedx/app/MainFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ class MainFragment : Fragment(R.layout.fragment_main) {
binding.bottomNavView.setOnItemSelectedListener {
when (it.itemId) {
R.id.fragmentLearn -> {
viewModel.logMyCoursesTabClickedEvent()
binding.viewPager.setCurrentItem(0, false)
}

Expand Down
16 changes: 6 additions & 10 deletions app/src/main/java/org/openedx/app/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import org.openedx.core.BaseViewModel
import org.openedx.core.config.Config
import org.openedx.core.system.notifier.DiscoveryNotifier
import org.openedx.core.system.notifier.NavigationToDiscovery
import org.openedx.dashboard.presentation.DashboardRouter
import org.openedx.discovery.presentation.DiscoveryNavigator

class MainViewModel(
Expand Down Expand Up @@ -51,20 +50,17 @@ class MainViewModel(
}

fun logDiscoveryTabClickedEvent() {
logEvent(AppAnalyticsEvent.DISCOVER)
}

fun logMyCoursesTabClickedEvent() {
logEvent(AppAnalyticsEvent.MY_COURSES)
logScreenEvent(AppAnalyticsEvent.DISCOVER)
}

fun logProfileTabClickedEvent() {
logEvent(AppAnalyticsEvent.PROFILE)
logScreenEvent(AppAnalyticsEvent.PROFILE)
}

private fun logEvent(event: AppAnalyticsEvent) {
analytics.logEvent(event.eventName,
buildMap {
private fun logScreenEvent(event: AppAnalyticsEvent) {
analytics.logScreenEvent(
screenName = event.eventName,
params = buildMap {
put(AppAnalyticsKey.NAME.key, event.biValue)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class FirebaseAnalytics(context: Context) : Analytics {
}

override fun logScreenEvent(screenName: String, params: Map<String, Any?>) {
tracker.logEvent(screenName, params.toBundle())
logger.d { "Firebase Analytics log Screen Event: $screenName + $params" }
}

Expand Down
41 changes: 41 additions & 0 deletions app/src/main/java/org/openedx/app/analytics/FullstoryAnalytics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.openedx.app.analytics

import com.fullstory.FS
import com.fullstory.FSSessionData
import org.openedx.core.utils.Logger

class FullstoryAnalytics : Analytics {

private val logger = Logger(TAG)

init {
FS.setReadyListener { sessionData: FSSessionData ->
val sessionUrl = sessionData.currentSessionURL
logger.d { "FullStory Session URL is: $sessionUrl" }
}
}

override fun logScreenEvent(screenName: String, params: Map<String, Any?>) {
logger.d { "Page : $screenName $params" }
FS.page(screenName, params).start()
}

override fun logEvent(eventName: String, params: Map<String, Any?>) {
logger.d { "Event: $eventName $params" }
FS.page(eventName, params).start()
}

override fun logUserId(userId: Long) {
logger.d { "Identify: $userId" }
FS.identify(
userId.toString(), mapOf(
DISPLAY_NAME to userId
)
)
}

private companion object {
const val TAG = "FullstoryAnalytics"
private const val DISPLAY_NAME = "displayName"
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ val screenModule = module {
)
}
viewModel { AllEnrolledCoursesViewModel(get(), get(), get(), get(), get(), get(), get()) }
viewModel { LearnViewModel(get(), get()) }
viewModel { LearnViewModel(get(), get(), get()) }

factory { DiscoveryRepository(get(), get(), get()) }
factory { DiscoveryInteractor(get()) }
Expand Down
13 changes: 13 additions & 0 deletions auth/src/main/java/org/openedx/auth/presentation/AuthAnalytics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package org.openedx.auth.presentation
interface AuthAnalytics {
fun setUserIdForSession(userId: Long)
fun logEvent(event: String, params: Map<String, Any?>)
fun logScreenEvent(screenName: String, params: Map<String, Any?>)
}

enum class AuthAnalyticsEvent(val eventName: String, val biValue: String) {
Logistration(
"Logistration",
"edx.bi.app.logistration"
),
DISCOVERY_COURSES_SEARCH(
"Logistration:Courses Search",
"edx.bi.app.logistration.courses_search"
Expand All @@ -14,6 +19,14 @@ enum class AuthAnalyticsEvent(val eventName: String, val biValue: String) {
"Logistration:Explore All Courses",
"edx.bi.app.logistration.explore.all.courses"
),
SIGN_IN(
"Logistration:Sign In",
"edx.bi.app.logistration.signin"
),
REGISTER(
"Logistration:Register",
"edx.bi.app.logistration.register"
),
REGISTER_CLICKED(
"Logistration:Register Clicked",
"edx.bi.app.logistration.register.clicked"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class LogistrationViewModel(

private val discoveryTypeWebView get() = config.getDiscoveryConfig().isViewTypeWebView()

init {
logLogistrationScreenEvent()
}

fun navigateToSignIn(parentFragmentManager: FragmentManager) {
router.navigateToSignIn(parentFragmentManager, courseId, null)
logEvent(AuthAnalyticsEvent.SIGN_IN_CLICKED)
Expand Down Expand Up @@ -62,4 +66,14 @@ class LogistrationViewModel(
}
)
}

private fun logLogistrationScreenEvent() {
val event = AuthAnalyticsEvent.Logistration
analytics.logScreenEvent(
screenName = event.eventName,
params = buildMap {
put(AuthAnalyticsKey.NAME.key, event.biValue)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class SignInViewModel(

init {
collectAppUpgradeEvent()
logSignInScreenEvent()
}

fun login(username: String, password: String) {
Expand Down Expand Up @@ -245,4 +246,14 @@ class SignInViewModel(
}
)
}

private fun logSignInScreenEvent() {
val event = AuthAnalyticsEvent.SIGN_IN
analytics.logScreenEvent(
screenName = event.eventName,
params = buildMap {
put(AuthAnalyticsKey.NAME.key, event.biValue)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SignUpViewModel(

init {
collectAppUpgradeEvent()
logRegisterScreenEvent()
}

fun getRegistrationFields() {
Expand Down Expand Up @@ -324,4 +325,14 @@ class SignUpViewModel(
}
)
}

private fun logRegisterScreenEvent() {
val event = AuthAnalyticsEvent.REGISTER
analytics.logScreenEvent(
screenName = event.eventName,
params = buildMap {
put(AuthAnalyticsKey.NAME.key, event.biValue)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class SignInViewModelTest {
every { config.getFacebookConfig() } returns FacebookConfig()
every { config.getGoogleConfig() } returns GoogleConfig()
every { config.getMicrosoftConfig() } returns MicrosoftConfig()
every { analytics.logScreenEvent(any(), any()) } returns Unit
}

@After
Expand Down Expand Up @@ -119,6 +120,7 @@ class SignInViewModelTest {
coVerify(exactly = 0) { interactor.login(any(), any()) }
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }

val message = viewModel.uiMessage.value as UIMessage.SnackBarMessage
val uiState = viewModel.uiState.value
Expand Down Expand Up @@ -220,6 +222,7 @@ class SignInViewModelTest {
coVerify(exactly = 0) { interactor.login(any(), any()) }
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }

val message = viewModel.uiMessage.value as UIMessage.SnackBarMessage
val uiState = viewModel.uiState.value
Expand Down Expand Up @@ -258,6 +261,7 @@ class SignInViewModelTest {
coVerify(exactly = 1) { interactor.login(any(), any()) }
verify(exactly = 1) { analytics.setUserIdForSession(any()) }
verify(exactly = 2) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }
verify(exactly = 1) { appNotifier.notifier }
val uiState = viewModel.uiState.value
assertFalse(uiState.showProgress)
Expand Down Expand Up @@ -294,6 +298,7 @@ class SignInViewModelTest {
coVerify(exactly = 1) { interactor.login(any(), any()) }
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }
verify(exactly = 1) { appNotifier.notifier }

val message = viewModel.uiMessage.value as? UIMessage.SnackBarMessage
Expand Down Expand Up @@ -333,6 +338,7 @@ class SignInViewModelTest {
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
verify(exactly = 1) { appNotifier.notifier }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }

val message = viewModel.uiMessage.value as UIMessage.SnackBarMessage
val uiState = viewModel.uiState.value
Expand Down Expand Up @@ -371,6 +377,7 @@ class SignInViewModelTest {
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
verify(exactly = 1) { appNotifier.notifier }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }

val message = viewModel.uiMessage.value as UIMessage.SnackBarMessage
val uiState = viewModel.uiState.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class SignUpViewModelTest {
every { config.getGoogleConfig() } returns GoogleConfig()
every { config.getMicrosoftConfig() } returns MicrosoftConfig()
every { config.getMicrosoftConfig() } returns MicrosoftConfig()
every { analytics.logScreenEvent(any(), any()) } returns Unit
}

@After
Expand Down Expand Up @@ -159,6 +160,7 @@ class SignUpViewModelTest {
advanceUntilIdle()
coVerify(exactly = 1) { interactor.validateRegistrationFields(any()) }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }
coVerify(exactly = 0) { interactor.register(any()) }
coVerify(exactly = 0) { interactor.login(any(), any()) }
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
Expand Down Expand Up @@ -206,6 +208,7 @@ class SignUpViewModelTest {
viewModel.register()
advanceUntilIdle()
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
coVerify(exactly = 1) { interactor.validateRegistrationFields(any()) }
coVerify(exactly = 0) { interactor.register(any()) }
Expand Down Expand Up @@ -245,6 +248,7 @@ class SignUpViewModelTest {
advanceUntilIdle()
verify(exactly = 0) { analytics.setUserIdForSession(any()) }
verify(exactly = 1) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }
coVerify(exactly = 1) { interactor.validateRegistrationFields(any()) }
coVerify(exactly = 0) { interactor.register(any()) }
coVerify(exactly = 0) { interactor.login(any(), any()) }
Expand Down Expand Up @@ -298,6 +302,7 @@ class SignUpViewModelTest {
coVerify(exactly = 1) { interactor.register(any()) }
coVerify(exactly = 1) { interactor.login(any(), any()) }
verify(exactly = 2) { analytics.logEvent(any(), any()) }
verify(exactly = 1) { analytics.logScreenEvent(any(), any()) }
verify(exactly = 1) { appNotifier.notifier }

assertFalse(viewModel.uiState.value.validationError)
Expand Down
Loading
Loading