From 51a90ee65555c31b74261af3243711010c306195 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Thu, 26 Oct 2023 12:53:26 +0200 Subject: [PATCH 01/18] analytics --- app/build.gradle | 5 +++ .../loudius/analytics/AnalyticsService.kt | 35 +++++++++++++++++++ .../appunite/loudius/di/AnalyticsModule.kt | 29 +++++++++++++++ .../ui/pullrequests/PullRequestsViewModel.kt | 14 +++++++- .../ui/reviewers/ReviewersViewModel.kt | 15 +++++++- build.gradle | 1 + 6 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt create mode 100644 app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt diff --git a/app/build.gradle b/app/build.gradle index 3228ece7a..843f11089 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ plugins { id 'kotlin-kapt' id 'org.jetbrains.kotlin.android' id 'com.google.dagger.hilt.android' + id 'com.google.gms.google-services' id 'org.jlleitschuh.gradle.ktlint' version '11.6.1' id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.0' } @@ -165,6 +166,10 @@ dependencies { // ktlint ktlintRuleset project(":custom-ktlint-rules") + + // Firebase + implementation(platform("com.google.firebase:firebase-bom:32.4.0")) + implementation("com.google.firebase:firebase-analytics-ktx") } tasks.withType(Test) { diff --git a/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt b/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt new file mode 100644 index 000000000..a95aa4de9 --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +import android.os.Bundle +import com.google.firebase.analytics.FirebaseAnalytics + +interface AnalyticsService { + + fun logEvent(eventName: String, param: String, value: String) +} + +class AnalyticsServiceImpl(private val firebaseAnalytics: FirebaseAnalytics) : AnalyticsService { + + override fun logEvent(eventName: String, param: String, value: String) { + firebaseAnalytics.logEvent(eventName, createBundle(param, value)) + } + + private fun createBundle(key: String, value: String): Bundle = + Bundle().apply { putString(key, value) } +} diff --git a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt new file mode 100644 index 000000000..e32407102 --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt @@ -0,0 +1,29 @@ +package com.appunite.loudius.di + +import android.content.Context +import com.appunite.loudius.analytics.AnalyticsService +import com.appunite.loudius.analytics.AnalyticsServiceImpl +import com.google.firebase.analytics.FirebaseAnalytics +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +class AnalyticsModule { + + @Provides + @Singleton + fun provideAnalyticsService( + analytics: FirebaseAnalytics + ): AnalyticsService = AnalyticsServiceImpl(analytics) + + @Provides + @Singleton + fun provideFirebaseAnalytics( + @ApplicationContext context: Context + ): FirebaseAnalytics = FirebaseAnalytics.getInstance(context) +} diff --git a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt index bb6896140..bd311d7fe 100644 --- a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt +++ b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt @@ -21,8 +21,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.domain.repository.PullRequestRepository import com.appunite.loudius.network.model.PullRequest +import com.google.firebase.analytics.FirebaseAnalytics import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -53,7 +55,8 @@ data class NavigationPayload( @HiltViewModel class PullRequestsViewModel @Inject constructor( - private val pullRequestsRepository: PullRequestRepository + private val pullRequestsRepository: PullRequestRepository, + private val analyticsService: AnalyticsService ) : ViewModel() { var state: PullRequestState by mutableStateOf(PullRequestState()) private set @@ -108,9 +111,18 @@ class PullRequestsViewModel @Inject constructor( itemClickedData.createdAt.toString() ) ) + trackNavigateToReviewersEvent() } private fun resetNavigationState() { state = state.copy(navigateToReviewers = null) } + + private fun trackNavigateToReviewersEvent() { + analyticsService.logEvent( + eventName = FirebaseAnalytics.Event.SELECT_ITEM, + param = FirebaseAnalytics.Param.CONTENT, + value = "navigate_to_reviewers" + ) + } } diff --git a/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt b/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt index 1585afff8..f0ee4846c 100644 --- a/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt +++ b/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.common.Screen.Reviewers.getInitialValues import com.appunite.loudius.common.flatMap import com.appunite.loudius.domain.repository.PullRequestRepository @@ -29,6 +30,7 @@ import com.appunite.loudius.network.model.RequestedReviewersResponse import com.appunite.loudius.network.model.Review import com.appunite.loudius.ui.reviewers.ReviewersSnackbarType.FAILURE import com.appunite.loudius.ui.reviewers.ReviewersSnackbarType.SUCCESS +import com.google.firebase.analytics.FirebaseAnalytics import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope @@ -66,7 +68,8 @@ enum class ReviewersSnackbarType { @HiltViewModel class ReviewersViewModel @Inject constructor( private val repository: PullRequestRepository, - savedStateHandle: SavedStateHandle + savedStateHandle: SavedStateHandle, + private val analyticsService: AnalyticsService ) : ViewModel() { private val initialValues = getInitialValues(savedStateHandle) @@ -213,6 +216,7 @@ class ReviewersViewModel @Inject constructor( reviewers = successData.reviewers.updateLoadingState(userLogin, false) ) ) + trackNotifyReviewerEvent("notify_reviewer_failure") } private fun onNotifyUserSuccess( @@ -225,6 +229,7 @@ class ReviewersViewModel @Inject constructor( successData.reviewers.updateLoadingState(userLogin, false) ) ) + trackNotifyReviewerEvent("notify_reviewer_success") } private fun List.updateLoadingState( @@ -241,4 +246,12 @@ class ReviewersViewModel @Inject constructor( private fun dismissSnackbar() { state = state.copy(snackbarTypeShown = null) } + + private fun trackNotifyReviewerEvent(value: String) { + analyticsService.logEvent( + eventName = FirebaseAnalytics.Event.SELECT_ITEM, + param = FirebaseAnalytics.Param.CONTENT, + value = value + ) + } } diff --git a/build.gradle b/build.gradle index 60074d581..2f95e7c5e 100644 --- a/build.gradle +++ b/build.gradle @@ -13,4 +13,5 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.9.0' apply false id 'com.google.dagger.hilt.android' version '2.48' apply false id 'org.jetbrains.kotlin.jvm' version '1.9.0' apply false + id 'com.google.gms.google-services' version '4.4.0' apply false } From 3966243b7bf92437126a09df5a41e575cb830893 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Thu, 26 Oct 2023 14:01:00 +0200 Subject: [PATCH 02/18] hilt to koin --- app/build.gradle | 1 - .../appunite/loudius/LoudiusApplication.kt | 4 ++- .../appunite/loudius/di/AnalyticsModule.kt | 31 +++++-------------- build.gradle | 1 - 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9012f2747..3f24663be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,6 @@ plugins { id 'com.android.application' id 'kotlin-kapt' id 'org.jetbrains.kotlin.android' - id 'com.google.dagger.hilt.android' id 'com.google.gms.google-services' id 'org.jlleitschuh.gradle.ktlint' version '11.6.1' id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.0' diff --git a/app/src/main/java/com/appunite/loudius/LoudiusApplication.kt b/app/src/main/java/com/appunite/loudius/LoudiusApplication.kt index d040e3e77..44d741f7e 100644 --- a/app/src/main/java/com/appunite/loudius/LoudiusApplication.kt +++ b/app/src/main/java/com/appunite/loudius/LoudiusApplication.kt @@ -17,6 +17,7 @@ package com.appunite.loudius import android.app.Application +import com.appunite.loudius.di.analyticsModule import com.appunite.loudius.di.dataSourceModule import com.appunite.loudius.di.dispatcherModule import com.appunite.loudius.di.githubHelperModule @@ -36,7 +37,8 @@ val appModule = module { serviceModule, repositoryModule, githubHelperModule, - dispatcherModule + dispatcherModule, + analyticsModule ) } diff --git a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt index e32407102..5f8f67cc4 100644 --- a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt +++ b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt @@ -1,29 +1,14 @@ package com.appunite.loudius.di -import android.content.Context import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.analytics.AnalyticsServiceImpl import com.google.firebase.analytics.FirebaseAnalytics -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@InstallIn(SingletonComponent::class) -@Module -class AnalyticsModule { - - @Provides - @Singleton - fun provideAnalyticsService( - analytics: FirebaseAnalytics - ): AnalyticsService = AnalyticsServiceImpl(analytics) - - @Provides - @Singleton - fun provideFirebaseAnalytics( - @ApplicationContext context: Context - ): FirebaseAnalytics = FirebaseAnalytics.getInstance(context) +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val analyticsModule = module { + single { + val firebaseAnalytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(androidContext()) + AnalyticsServiceImpl(firebaseAnalytics) + } } diff --git a/build.gradle b/build.gradle index 1e55925bc..6b976dad7 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,6 @@ plugins { id 'com.android.application' version '8.1.2' apply false id 'com.android.library' version '8.1.2' apply false id 'org.jetbrains.kotlin.android' version '1.9.0' apply false - id 'com.google.dagger.hilt.android' version '2.48' apply false id 'org.jetbrains.kotlin.jvm' version '1.9.0' apply false id 'com.google.gms.google-services' version '4.4.0' apply false } From ef327f9d94ad3a461813bac3fb95864b800779ac Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Fri, 27 Oct 2023 09:48:14 +0200 Subject: [PATCH 03/18] adjust tests --- .../java/com/appunite/loudius/di/AnalyticsModule.kt | 4 ++-- .../java/com/appunite/loudius/di/CheckModulesTest.kt | 10 ++++++++-- .../ui/pullrequests/PullRequestsViewModelTest.kt | 5 ++++- .../loudius/ui/reviewers/ReviewersViewModelTest.kt | 4 +++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt index 5f8f67cc4..43d5e907d 100644 --- a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt +++ b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt @@ -8,7 +8,7 @@ import org.koin.dsl.module val analyticsModule = module { single { - val firebaseAnalytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(androidContext()) - AnalyticsServiceImpl(firebaseAnalytics) + AnalyticsServiceImpl(get()) } + single { FirebaseAnalytics.getInstance(androidContext()) } } diff --git a/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt b/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt index 323104596..a3fde2549 100644 --- a/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt +++ b/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt @@ -20,8 +20,11 @@ import android.content.Context import android.content.SharedPreferences import com.appunite.loudius.appModule import com.appunite.loudius.util.MainDispatcherExtension +import com.google.firebase.analytics.FirebaseAnalytics import io.mockk.every +import io.mockk.mockk import io.mockk.mockkClass +import io.mockk.mockkStatic import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.koin.dsl.koinApplication @@ -35,15 +38,18 @@ class CheckModulesTest : KoinTest { fun verifyKoinApp() { val mockContext = mockkClass(Context::class) val mockSharedPref = mockkClass(SharedPreferences::class) - every { mockContext.getSharedPreferences(any(), any()) } returns mockSharedPref + val mockFirebaseAnalytics = mockk() + mockkStatic(FirebaseAnalytics::class) + every { FirebaseAnalytics.getInstance(any()) } returns mockFirebaseAnalytics + koinApplication { modules( appModule ) - checkModules() { + checkModules { withInstance(mockContext) } } diff --git a/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt index 940a8ee0e..1484000aa 100644 --- a/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt @@ -16,6 +16,7 @@ package com.appunite.loudius.ui.pullrequests +import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.fakes.FakePullRequestRepository import com.appunite.loudius.network.utils.WebException import com.appunite.loudius.util.Defaults @@ -23,6 +24,7 @@ import com.appunite.loudius.util.MainDispatcherExtension import com.appunite.loudius.util.neverCompletingSuspension import io.mockk.clearMocks import io.mockk.coEvery +import io.mockk.mockk import io.mockk.spyk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -40,7 +42,8 @@ import strikt.assertions.single @ExtendWith(MainDispatcherExtension::class) class PullRequestsViewModelTest { private val pullRequestRepository = spyk(FakePullRequestRepository()) - private fun createViewModel() = PullRequestsViewModel(pullRequestRepository) + private val analyticsService = mockk(relaxed = true) + private fun createViewModel() = PullRequestsViewModel(pullRequestRepository, analyticsService) @Test fun `WHEN refresh data THEN start refreshing data and set isRefreshing to true`() = runTest { diff --git a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt index 2e13046be..441993aef 100644 --- a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt @@ -17,6 +17,7 @@ package com.appunite.loudius.ui.reviewers import androidx.lifecycle.SavedStateHandle +import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.fakes.FakePullRequestRepository import com.appunite.loudius.network.model.RequestedReviewersResponse import com.appunite.loudius.network.utils.WebException @@ -61,8 +62,9 @@ class ReviewersViewModelTest { every { get("pull_request_number") } returns "correctPullRequestNumber" } private lateinit var viewModel: ReviewersViewModel + private val analyticsService = mockk(relaxed = true) - private fun createViewModel() = ReviewersViewModel(repository, savedStateHandle) + private fun createViewModel() = ReviewersViewModel(repository, savedStateHandle, analyticsService) @BeforeEach fun setup() { From 1bf2f67965f931882c697a1412e5adebd5e77ed7 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Sun, 29 Oct 2023 18:39:24 +0100 Subject: [PATCH 04/18] Event tracker class for analytics. --- .../loudius/analytics/AnalyticsService.kt | 16 ++-- .../loudius/analytics/BundleBuilder.kt | 38 +++++++++ .../analytics/ReviewersEventTracker.kt | 80 +++++++++++++++++++ .../appunite/loudius/di/AnalyticsModule.kt | 8 +- .../ui/pullrequests/PullRequestsViewModel.kt | 14 +--- .../ui/reviewers/ReviewersViewModel.kt | 40 ++++++---- .../pullrequests/PullRequestsViewModelTest.kt | 7 +- .../ui/reviewers/ReviewersViewModelTest.kt | 6 +- 8 files changed, 161 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt create mode 100644 app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt diff --git a/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt b/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt index a95aa4de9..ee5b0928e 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt @@ -16,20 +16,20 @@ package com.appunite.loudius.analytics -import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics interface AnalyticsService { - fun logEvent(eventName: String, param: String, value: String) + fun logEvent(eventName: String, builder: BundleBuilder.() -> Unit) } -class AnalyticsServiceImpl(private val firebaseAnalytics: FirebaseAnalytics) : AnalyticsService { +class AnalyticsServiceImpl( + private val firebaseAnalytics: FirebaseAnalytics +) : AnalyticsService { - override fun logEvent(eventName: String, param: String, value: String) { - firebaseAnalytics.logEvent(eventName, createBundle(param, value)) + override fun logEvent(eventName: String, builder: BundleBuilder.() -> Unit) { + val bundleBuilder = BundleBuilderImpl() + builder(bundleBuilder) + firebaseAnalytics.logEvent(eventName, bundleBuilder.bundle) } - - private fun createBundle(key: String, value: String): Bundle = - Bundle().apply { putString(key, value) } } diff --git a/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt b/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt new file mode 100644 index 000000000..c5c3a10e0 --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +import android.os.Bundle + +interface BundleBuilder { + + fun param(key: String, value: String) + fun param(key: String, value: Int) +} + +class BundleBuilderImpl : BundleBuilder { + + val bundle = Bundle() + + override fun param(key: String, value: String) { + bundle.putString(key, value) + } + + override fun param(key: String, value: Int) { + bundle.putInt(key, value) + } +} diff --git a/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt b/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt new file mode 100644 index 000000000..c4a037df9 --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +class ReviewersEventTracker(private val analyticsService: AnalyticsService) { + + fun trackNotifySuccess() { + analyticsService.logEvent(eventName = "action_finished") { + param("item_name", "notify") + param("success", "true") + } + } + + fun trackNotifyFailure() { + analyticsService.logEvent(eventName = "action_finished") { + param("item_name", "notify") + param("success", "false") + } + } + + fun trackClickNotify() { + analyticsService.logEvent(eventName = "button_click") { + param("item_name", "notify") + } + } + + fun trackRefreshData() { + analyticsService.logEvent(eventName = "action_start") { + param("item_name", "refresh_data") + } + } + + fun trackRefreshDataSuccess() { + analyticsService.logEvent(eventName = "action_finished") { + param("item_name", "refresh_data") + param("success", "true") + } + } + + fun trackRefreshDataFailure() { + analyticsService.logEvent(eventName = "action_finished") { + param("item_name", "refresh_data") + param("success", "false") + } + } + + fun trackFetchData() { + analyticsService.logEvent(eventName = "action_start") { + param("item_name", "fetch_data") + } + } + + fun trackFetchDataSuccess() { + analyticsService.logEvent(eventName = "action_finished") { + param("item_name", "fetch_data") + param("success", "true") + } + } + + fun trackFetchDataFailure() { + analyticsService.logEvent(eventName = "action_finished") { + param("item_name", "fetch_data") + param("success", "false") + } + } +} diff --git a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt index 43d5e907d..eec8b4043 100644 --- a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt +++ b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt @@ -2,6 +2,7 @@ package com.appunite.loudius.di import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.analytics.AnalyticsServiceImpl +import com.appunite.loudius.analytics.ReviewersEventTracker import com.google.firebase.analytics.FirebaseAnalytics import org.koin.android.ext.koin.androidContext import org.koin.dsl.module @@ -10,5 +11,10 @@ val analyticsModule = module { single { AnalyticsServiceImpl(get()) } - single { FirebaseAnalytics.getInstance(androidContext()) } + single { + FirebaseAnalytics.getInstance(androidContext()) + } + single { + ReviewersEventTracker(get()) + } } diff --git a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt index cbaa17d90..05f9fc1c7 100644 --- a/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt +++ b/app/src/main/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModel.kt @@ -21,10 +21,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.domain.repository.PullRequestRepository import com.appunite.loudius.network.model.PullRequest -import com.google.firebase.analytics.FirebaseAnalytics import kotlinx.coroutines.launch sealed class PulLRequestsAction { @@ -52,8 +50,7 @@ data class NavigationPayload( ) class PullRequestsViewModel( - private val pullRequestsRepository: PullRequestRepository, - private val analyticsService: AnalyticsService + private val pullRequestsRepository: PullRequestRepository ) : ViewModel() { var state: PullRequestState by mutableStateOf(PullRequestState()) private set @@ -108,18 +105,9 @@ class PullRequestsViewModel( itemClickedData.createdAt.toString() ) ) - trackNavigateToReviewersEvent() } private fun resetNavigationState() { state = state.copy(navigateToReviewers = null) } - - private fun trackNavigateToReviewersEvent() { - analyticsService.logEvent( - eventName = FirebaseAnalytics.Event.SELECT_ITEM, - param = FirebaseAnalytics.Param.CONTENT, - value = "navigate_to_reviewers" - ) - } } diff --git a/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt b/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt index 67eb24adb..5ea301097 100644 --- a/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt +++ b/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt @@ -22,7 +22,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.appunite.loudius.analytics.AnalyticsService +import com.appunite.loudius.analytics.ReviewersEventTracker import com.appunite.loudius.common.Screen.Reviewers.getInitialValues import com.appunite.loudius.common.flatMap import com.appunite.loudius.domain.repository.PullRequestRepository @@ -30,7 +30,6 @@ import com.appunite.loudius.network.model.RequestedReviewersResponse import com.appunite.loudius.network.model.Review import com.appunite.loudius.ui.reviewers.ReviewersSnackbarType.FAILURE import com.appunite.loudius.ui.reviewers.ReviewersSnackbarType.SUCCESS -import com.google.firebase.analytics.FirebaseAnalytics import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -66,7 +65,7 @@ enum class ReviewersSnackbarType { class ReviewersViewModel( private val repository: PullRequestRepository, savedStateHandle: SavedStateHandle, - private val analyticsService: AnalyticsService + private val eventTracker: ReviewersEventTracker ) : ViewModel() { private val initialValues = getInitialValues(savedStateHandle) @@ -82,22 +81,36 @@ class ReviewersViewModel( } fun refreshData() { + eventTracker.trackRefreshData() viewModelScope.launch { _isRefreshing.value = true getMergedData() - .onSuccess { state = state.copy(data = Data.Success(reviewers = it)) } - .onFailure { state = state.copy(data = Data.Error) } + .onSuccess { + state = state.copy(data = Data.Success(reviewers = it)) + eventTracker.trackRefreshDataSuccess() + } + .onFailure { + state = state.copy(data = Data.Error) + eventTracker.trackRefreshDataFailure() + } _isRefreshing.value = false } } private fun fetchData() { + eventTracker.trackFetchData() viewModelScope.launch { state = state.copy(data = Data.Loading) getMergedData() - .onSuccess { state = state.copy(data = Data.Success(reviewers = it)) } - .onFailure { state = state.copy(data = Data.Error) } + .onSuccess { + state = state.copy(data = Data.Success(reviewers = it)) + eventTracker.trackFetchDataSuccess() + } + .onFailure { + state = state.copy(data = Data.Error) + eventTracker.trackFetchDataFailure() + } } } @@ -180,6 +193,7 @@ class ReviewersViewModel( } private fun notifyReviewer(userLogin: String) { + eventTracker.trackClickNotify() val (owner, repo, pullRequestNumber) = initialValues val successData = state.data as? Data.Success ?: return @@ -213,7 +227,7 @@ class ReviewersViewModel( reviewers = successData.reviewers.updateLoadingState(userLogin, false) ) ) - trackNotifyReviewerEvent("notify_reviewer_failure") + eventTracker.trackNotifyFailure() } private fun onNotifyUserSuccess( @@ -226,7 +240,7 @@ class ReviewersViewModel( successData.reviewers.updateLoadingState(userLogin, false) ) ) - trackNotifyReviewerEvent("notify_reviewer_success") + eventTracker.trackNotifySuccess() } private fun List.updateLoadingState( @@ -243,12 +257,4 @@ class ReviewersViewModel( private fun dismissSnackbar() { state = state.copy(snackbarTypeShown = null) } - - private fun trackNotifyReviewerEvent(value: String) { - analyticsService.logEvent( - eventName = FirebaseAnalytics.Event.SELECT_ITEM, - param = FirebaseAnalytics.Param.CONTENT, - value = value - ) - } } diff --git a/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt index 1484000aa..65bacec47 100644 --- a/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/pullrequests/PullRequestsViewModelTest.kt @@ -16,7 +16,6 @@ package com.appunite.loudius.ui.pullrequests -import com.appunite.loudius.analytics.AnalyticsService import com.appunite.loudius.fakes.FakePullRequestRepository import com.appunite.loudius.network.utils.WebException import com.appunite.loudius.util.Defaults @@ -24,9 +23,7 @@ import com.appunite.loudius.util.MainDispatcherExtension import com.appunite.loudius.util.neverCompletingSuspension import io.mockk.clearMocks import io.mockk.coEvery -import io.mockk.mockk import io.mockk.spyk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -38,12 +35,10 @@ import strikt.assertions.isNull import strikt.assertions.isTrue import strikt.assertions.single -@OptIn(ExperimentalCoroutinesApi::class) @ExtendWith(MainDispatcherExtension::class) class PullRequestsViewModelTest { private val pullRequestRepository = spyk(FakePullRequestRepository()) - private val analyticsService = mockk(relaxed = true) - private fun createViewModel() = PullRequestsViewModel(pullRequestRepository, analyticsService) + private fun createViewModel() = PullRequestsViewModel(pullRequestRepository) @Test fun `WHEN refresh data THEN start refreshing data and set isRefreshing to true`() = runTest { diff --git a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt index 441993aef..8d2d96546 100644 --- a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt @@ -17,7 +17,7 @@ package com.appunite.loudius.ui.reviewers import androidx.lifecycle.SavedStateHandle -import com.appunite.loudius.analytics.AnalyticsService +import com.appunite.loudius.analytics.ReviewersEventTracker import com.appunite.loudius.fakes.FakePullRequestRepository import com.appunite.loudius.network.model.RequestedReviewersResponse import com.appunite.loudius.network.utils.WebException @@ -62,9 +62,9 @@ class ReviewersViewModelTest { every { get("pull_request_number") } returns "correctPullRequestNumber" } private lateinit var viewModel: ReviewersViewModel - private val analyticsService = mockk(relaxed = true) + private val eventTracker = mockk(relaxed = true) - private fun createViewModel() = ReviewersViewModel(repository, savedStateHandle, analyticsService) + private fun createViewModel() = ReviewersViewModel(repository, savedStateHandle, eventTracker) @BeforeEach fun setup() { From 6b7b983282feb2aacc09630bb35a0cad26fb3bf8 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Sun, 29 Oct 2023 18:54:47 +0100 Subject: [PATCH 05/18] Code cleanup --- .../com/appunite/loudius/analytics/BundleBuilder.kt | 6 +++--- .../loudius/analytics/ReviewersEventTracker.kt | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt b/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt index c5c3a10e0..24aab5edb 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt @@ -21,7 +21,7 @@ import android.os.Bundle interface BundleBuilder { fun param(key: String, value: String) - fun param(key: String, value: Int) + fun param(key: String, value: Boolean) } class BundleBuilderImpl : BundleBuilder { @@ -32,7 +32,7 @@ class BundleBuilderImpl : BundleBuilder { bundle.putString(key, value) } - override fun param(key: String, value: Int) { - bundle.putInt(key, value) + override fun param(key: String, value: Boolean) { + bundle.putBoolean(key, value) } } diff --git a/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt b/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt index c4a037df9..9867b21b4 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt @@ -21,14 +21,14 @@ class ReviewersEventTracker(private val analyticsService: AnalyticsService) { fun trackNotifySuccess() { analyticsService.logEvent(eventName = "action_finished") { param("item_name", "notify") - param("success", "true") + param("success", true) } } fun trackNotifyFailure() { analyticsService.logEvent(eventName = "action_finished") { param("item_name", "notify") - param("success", "false") + param("success", false) } } @@ -47,14 +47,14 @@ class ReviewersEventTracker(private val analyticsService: AnalyticsService) { fun trackRefreshDataSuccess() { analyticsService.logEvent(eventName = "action_finished") { param("item_name", "refresh_data") - param("success", "true") + param("success", true) } } fun trackRefreshDataFailure() { analyticsService.logEvent(eventName = "action_finished") { param("item_name", "refresh_data") - param("success", "false") + param("success", false) } } @@ -67,14 +67,14 @@ class ReviewersEventTracker(private val analyticsService: AnalyticsService) { fun trackFetchDataSuccess() { analyticsService.logEvent(eventName = "action_finished") { param("item_name", "fetch_data") - param("success", "true") + param("success", true) } } fun trackFetchDataFailure() { analyticsService.logEvent(eventName = "action_finished") { param("item_name", "fetch_data") - param("success", "false") + param("success", false) } } } From fb482ee73c5248250d755b82d4eb3de50b279f42 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Sun, 29 Oct 2023 19:41:54 +0100 Subject: [PATCH 06/18] Add google services to github workflow --- .github/workflows/run-test.yml | 10 ++++++++++ .../com/appunite/loudius/di/AnalyticsModule.kt | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 0cc41dc8e..7de47e71b 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -32,6 +32,11 @@ jobs: with: lfs: true + - name: Add google-services.json + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + run: echo $GOOGLE_SERVICES_JSON > /home/runner/work/Loudius/Loudius/app/google-services.json + - name: Prepare Android Environment uses: ./.github/actions/prepare-android-env @@ -64,6 +69,11 @@ jobs: with: lfs: true + - name: Add google-services.json + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + run: echo $GOOGLE_SERVICES_JSON > /home/runner/work/Loudius/Loudius/app/google-services.json + - name: LFS-warning - Prevent large files that are not LFS tracked uses: ppremk/lfs-warning@v3.2 diff --git a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt index eec8b4043..f5b01bd4c 100644 --- a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt +++ b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.di import com.appunite.loudius.analytics.AnalyticsService From 506a4d2f4d85bf0a7093295ceff7d170966a9866 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Sun, 29 Oct 2023 19:47:45 +0100 Subject: [PATCH 07/18] Add create file workflow --- .github/workflows/run-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 7de47e71b..ff22444b9 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -32,6 +32,9 @@ jobs: with: lfs: true + - name: Create file + run: cat /home/runner/work/Loudius/Loudius/app/google-services.json | base64 + - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} @@ -69,6 +72,9 @@ jobs: with: lfs: true + - name: Create file + run: cat /home/runner/work/Loudius/Loudius/app/google-services.json | base64 + - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} From d96eadc7d46b4cc45cf4ba41cb6a30c43fb8e14c Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Mon, 30 Oct 2023 11:37:43 +0100 Subject: [PATCH 08/18] Example test with tracking order. --- .../appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt index 8d2d96546..06f602e81 100644 --- a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt @@ -30,6 +30,7 @@ import io.mockk.mockk import io.mockk.mockkObject import io.mockk.spyk import io.mockk.verify +import io.mockk.verifyOrder import kotlinx.coroutines.test.runTest import kotlinx.datetime.Clock import kotlinx.datetime.Instant @@ -87,6 +88,10 @@ class ReviewersViewModelTest { Reviewer(3, "user3", false, 7, null) ) } + verifyOrder { + eventTracker.trackRefreshData() + eventTracker.trackRefreshDataSuccess() + } } @Test From b620244df19bcb173d8ff8246dcf95597c005d0e Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Mon, 30 Oct 2023 14:57:06 +0100 Subject: [PATCH 09/18] New implementation for analytics. --- .../com/appunite/loudius/analytics/Event.kt | 22 +++++ .../{BundleBuilder.kt => EventParameter.kt} | 28 +++--- .../analytics/EventParametersConverter.kt | 33 +++++++ .../{AnalyticsService.kt => EventTracker.kt} | 17 ++-- .../analytics/ReviewersEventTracker.kt | 80 ---------------- .../analytics/events/ReviewersEvents.kt | 95 +++++++++++++++++++ .../loudius/analytics/util/EventsConstants.kt | 23 +++++ .../appunite/loudius/di/AnalyticsModule.kt | 13 +-- .../ui/reviewers/ReviewersViewModel.kt | 31 +++--- .../ui/reviewers/ReviewersViewModelTest.kt | 4 +- 10 files changed, 219 insertions(+), 127 deletions(-) create mode 100644 app/src/main/java/com/appunite/loudius/analytics/Event.kt rename app/src/main/java/com/appunite/loudius/analytics/{BundleBuilder.kt => EventParameter.kt} (61%) create mode 100644 app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt rename app/src/main/java/com/appunite/loudius/analytics/{AnalyticsService.kt => EventTracker.kt} (62%) delete mode 100644 app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt create mode 100644 app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt create mode 100644 app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt diff --git a/app/src/main/java/com/appunite/loudius/analytics/Event.kt b/app/src/main/java/com/appunite/loudius/analytics/Event.kt new file mode 100644 index 000000000..b325af9ed --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/Event.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +interface Event { + val name: String + val parameters: List> +} diff --git a/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt b/app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt similarity index 61% rename from app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt rename to app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt index 24aab5edb..32f87ffbb 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/BundleBuilder.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt @@ -16,23 +16,17 @@ package com.appunite.loudius.analytics -import android.os.Bundle - -interface BundleBuilder { - - fun param(key: String, value: String) - fun param(key: String, value: Boolean) +sealed class EventParameter { + abstract val name: String + abstract val value: T } -class BundleBuilderImpl : BundleBuilder { - - val bundle = Bundle() +data class StringEventParameter( + override val name: String, + override val value: String +) : EventParameter() - override fun param(key: String, value: String) { - bundle.putString(key, value) - } - - override fun param(key: String, value: Boolean) { - bundle.putBoolean(key, value) - } -} +data class BooleanEventParameter( + override val name: String, + override val value: Boolean +) : EventParameter() diff --git a/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt b/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt new file mode 100644 index 000000000..dcce0b8ce --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +import android.os.Bundle + +class EventParametersConverter { + + fun convert(parameters: List>): Bundle { + val bundle = Bundle() + for (parameter in parameters) { + when (parameter) { + is StringEventParameter -> bundle.putString(parameter.name, parameter.value) + is BooleanEventParameter -> bundle.putBoolean(parameter.name, parameter.value) + } + } + return bundle + } +} diff --git a/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt b/app/src/main/java/com/appunite/loudius/analytics/EventTracker.kt similarity index 62% rename from app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt rename to app/src/main/java/com/appunite/loudius/analytics/EventTracker.kt index ee5b0928e..89d50a5eb 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/AnalyticsService.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/EventTracker.kt @@ -18,18 +18,17 @@ package com.appunite.loudius.analytics import com.google.firebase.analytics.FirebaseAnalytics -interface AnalyticsService { +interface EventTracker { - fun logEvent(eventName: String, builder: BundleBuilder.() -> Unit) + fun trackEvent(event: Event) } -class AnalyticsServiceImpl( - private val firebaseAnalytics: FirebaseAnalytics -) : AnalyticsService { +class FirebaseAnalyticsEventTracker( + private val firebaseAnalytics: FirebaseAnalytics, + private val converter: EventParametersConverter +) : EventTracker { - override fun logEvent(eventName: String, builder: BundleBuilder.() -> Unit) { - val bundleBuilder = BundleBuilderImpl() - builder(bundleBuilder) - firebaseAnalytics.logEvent(eventName, bundleBuilder.bundle) + override fun trackEvent(event: Event) { + firebaseAnalytics.logEvent(event.name, converter.convert(event.parameters)) } } diff --git a/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt b/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt deleted file mode 100644 index 9867b21b4..000000000 --- a/app/src/main/java/com/appunite/loudius/analytics/ReviewersEventTracker.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023 AppUnite S.A. - * - * 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.appunite.loudius.analytics - -class ReviewersEventTracker(private val analyticsService: AnalyticsService) { - - fun trackNotifySuccess() { - analyticsService.logEvent(eventName = "action_finished") { - param("item_name", "notify") - param("success", true) - } - } - - fun trackNotifyFailure() { - analyticsService.logEvent(eventName = "action_finished") { - param("item_name", "notify") - param("success", false) - } - } - - fun trackClickNotify() { - analyticsService.logEvent(eventName = "button_click") { - param("item_name", "notify") - } - } - - fun trackRefreshData() { - analyticsService.logEvent(eventName = "action_start") { - param("item_name", "refresh_data") - } - } - - fun trackRefreshDataSuccess() { - analyticsService.logEvent(eventName = "action_finished") { - param("item_name", "refresh_data") - param("success", true) - } - } - - fun trackRefreshDataFailure() { - analyticsService.logEvent(eventName = "action_finished") { - param("item_name", "refresh_data") - param("success", false) - } - } - - fun trackFetchData() { - analyticsService.logEvent(eventName = "action_start") { - param("item_name", "fetch_data") - } - } - - fun trackFetchDataSuccess() { - analyticsService.logEvent(eventName = "action_finished") { - param("item_name", "fetch_data") - param("success", true) - } - } - - fun trackFetchDataFailure() { - analyticsService.logEvent(eventName = "action_finished") { - param("item_name", "fetch_data") - param("success", false) - } - } -} diff --git a/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt b/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt new file mode 100644 index 000000000..5d796bc9b --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics.events + +import com.appunite.loudius.analytics.BooleanEventParameter +import com.appunite.loudius.analytics.Event +import com.appunite.loudius.analytics.EventParameter +import com.appunite.loudius.analytics.StringEventParameter +import com.appunite.loudius.analytics.util.EventsConstants.ITEM_NAME +import com.appunite.loudius.analytics.util.EventsConstants.SUCCESS + +interface ReviewersEvent: Event + +object ClickNotifyEvent: ReviewersEvent { + override val name: String = "button_click" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "notify") + ) +} + +object NotifySuccessEvent: ReviewersEvent { + override val name: String = "action_finished" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "notify"), + BooleanEventParameter(SUCCESS, true) + ) +} + +object NotifyFailureEvent: ReviewersEvent { + override val name: String = "action_finished" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "notify"), + BooleanEventParameter(SUCCESS, false) + ) +} + +object RefreshDataEvent: ReviewersEvent { + override val name: String = "action_start" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "refresh_reviewers_data") + ) +} + +object RefreshDataSuccessEvent: ReviewersEvent { + override val name: String = "action_finished" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "refresh_reviewers_data"), + BooleanEventParameter(SUCCESS, true) + ) +} + +object RefreshDataFailureEvent: ReviewersEvent { + override val name: String = "action_finished" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "refresh_reviewers_data"), + BooleanEventParameter(SUCCESS, false) + ) +} + +object FetchDataEvent: ReviewersEvent { + override val name: String = "action_start" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "fetch_reviewers_data") + ) +} + +object FetchDataSuccessEvent: ReviewersEvent { + override val name: String = "action_finished" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "fetch_reviewers_data"), + BooleanEventParameter(SUCCESS, true) + ) +} + +object FetchDataFailureEvent: ReviewersEvent { + override val name: String = "action_finished" + override val parameters: List> = listOf( + StringEventParameter(ITEM_NAME, "fetch_reviewers_data"), + BooleanEventParameter(SUCCESS, false) + ) +} diff --git a/app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt b/app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt new file mode 100644 index 000000000..643b04fc7 --- /dev/null +++ b/app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics.util + +object EventsConstants { + + const val ITEM_NAME = "item_name" + const val SUCCESS = "success" +} diff --git a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt index f5b01bd4c..b8d4ae296 100644 --- a/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt +++ b/app/src/main/java/com/appunite/loudius/di/AnalyticsModule.kt @@ -16,21 +16,18 @@ package com.appunite.loudius.di -import com.appunite.loudius.analytics.AnalyticsService -import com.appunite.loudius.analytics.AnalyticsServiceImpl -import com.appunite.loudius.analytics.ReviewersEventTracker +import com.appunite.loudius.analytics.EventParametersConverter +import com.appunite.loudius.analytics.EventTracker +import com.appunite.loudius.analytics.FirebaseAnalyticsEventTracker import com.google.firebase.analytics.FirebaseAnalytics import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val analyticsModule = module { - single { - AnalyticsServiceImpl(get()) - } single { FirebaseAnalytics.getInstance(androidContext()) } - single { - ReviewersEventTracker(get()) + single { + FirebaseAnalyticsEventTracker(get(), EventParametersConverter()) } } diff --git a/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt b/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt index 5ea301097..8f2d569e6 100644 --- a/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt +++ b/app/src/main/java/com/appunite/loudius/ui/reviewers/ReviewersViewModel.kt @@ -22,7 +22,16 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.appunite.loudius.analytics.ReviewersEventTracker +import com.appunite.loudius.analytics.EventTracker +import com.appunite.loudius.analytics.events.ClickNotifyEvent +import com.appunite.loudius.analytics.events.FetchDataEvent +import com.appunite.loudius.analytics.events.FetchDataFailureEvent +import com.appunite.loudius.analytics.events.FetchDataSuccessEvent +import com.appunite.loudius.analytics.events.NotifyFailureEvent +import com.appunite.loudius.analytics.events.NotifySuccessEvent +import com.appunite.loudius.analytics.events.RefreshDataEvent +import com.appunite.loudius.analytics.events.RefreshDataFailureEvent +import com.appunite.loudius.analytics.events.RefreshDataSuccessEvent import com.appunite.loudius.common.Screen.Reviewers.getInitialValues import com.appunite.loudius.common.flatMap import com.appunite.loudius.domain.repository.PullRequestRepository @@ -65,7 +74,7 @@ enum class ReviewersSnackbarType { class ReviewersViewModel( private val repository: PullRequestRepository, savedStateHandle: SavedStateHandle, - private val eventTracker: ReviewersEventTracker + private val eventTracker: EventTracker ) : ViewModel() { private val initialValues = getInitialValues(savedStateHandle) @@ -81,35 +90,35 @@ class ReviewersViewModel( } fun refreshData() { - eventTracker.trackRefreshData() + eventTracker.trackEvent(RefreshDataEvent) viewModelScope.launch { _isRefreshing.value = true getMergedData() .onSuccess { state = state.copy(data = Data.Success(reviewers = it)) - eventTracker.trackRefreshDataSuccess() + eventTracker.trackEvent(RefreshDataSuccessEvent) } .onFailure { state = state.copy(data = Data.Error) - eventTracker.trackRefreshDataFailure() + eventTracker.trackEvent(RefreshDataFailureEvent) } _isRefreshing.value = false } } private fun fetchData() { - eventTracker.trackFetchData() + eventTracker.trackEvent(FetchDataEvent) viewModelScope.launch { state = state.copy(data = Data.Loading) getMergedData() .onSuccess { state = state.copy(data = Data.Success(reviewers = it)) - eventTracker.trackFetchDataSuccess() + eventTracker.trackEvent(FetchDataSuccessEvent) } .onFailure { state = state.copy(data = Data.Error) - eventTracker.trackFetchDataFailure() + eventTracker.trackEvent(FetchDataFailureEvent) } } } @@ -193,7 +202,7 @@ class ReviewersViewModel( } private fun notifyReviewer(userLogin: String) { - eventTracker.trackClickNotify() + eventTracker.trackEvent(ClickNotifyEvent) val (owner, repo, pullRequestNumber) = initialValues val successData = state.data as? Data.Success ?: return @@ -227,7 +236,7 @@ class ReviewersViewModel( reviewers = successData.reviewers.updateLoadingState(userLogin, false) ) ) - eventTracker.trackNotifyFailure() + eventTracker.trackEvent(NotifyFailureEvent) } private fun onNotifyUserSuccess( @@ -240,7 +249,7 @@ class ReviewersViewModel( successData.reviewers.updateLoadingState(userLogin, false) ) ) - eventTracker.trackNotifySuccess() + eventTracker.trackEvent(NotifySuccessEvent) } private fun List.updateLoadingState( diff --git a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt index 8d2d96546..33fdf8d21 100644 --- a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt @@ -17,7 +17,7 @@ package com.appunite.loudius.ui.reviewers import androidx.lifecycle.SavedStateHandle -import com.appunite.loudius.analytics.ReviewersEventTracker +import com.appunite.loudius.analytics.EventTracker import com.appunite.loudius.fakes.FakePullRequestRepository import com.appunite.loudius.network.model.RequestedReviewersResponse import com.appunite.loudius.network.utils.WebException @@ -62,7 +62,7 @@ class ReviewersViewModelTest { every { get("pull_request_number") } returns "correctPullRequestNumber" } private lateinit var viewModel: ReviewersViewModel - private val eventTracker = mockk(relaxed = true) + private val eventTracker = mockk(relaxed = true) private fun createViewModel() = ReviewersViewModel(repository, savedStateHandle, eventTracker) From 65a035fd3e4d93d2e549e81ad53c9e6a5ed00f54 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Tue, 31 Oct 2023 14:29:49 +0100 Subject: [PATCH 10/18] Add tests for converter, tracker and viewModel. --- .../ui/reviewers/ReviewersViewModelTest.kt | 118 +++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt index 3d603d390..5ebd770c5 100644 --- a/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt +++ b/app/src/test/java/com/appunite/loudius/ui/reviewers/ReviewersViewModelTest.kt @@ -18,6 +18,14 @@ package com.appunite.loudius.ui.reviewers import androidx.lifecycle.SavedStateHandle import com.appunite.loudius.analytics.EventTracker +import com.appunite.loudius.analytics.events.ClickNotifyEvent +import com.appunite.loudius.analytics.events.FetchDataEvent +import com.appunite.loudius.analytics.events.FetchDataFailureEvent +import com.appunite.loudius.analytics.events.FetchDataSuccessEvent +import com.appunite.loudius.analytics.events.NotifyFailureEvent +import com.appunite.loudius.analytics.events.NotifySuccessEvent +import com.appunite.loudius.analytics.events.RefreshDataEvent +import com.appunite.loudius.analytics.events.RefreshDataSuccessEvent import com.appunite.loudius.fakes.FakePullRequestRepository import com.appunite.loudius.network.model.RequestedReviewersResponse import com.appunite.loudius.network.utils.WebException @@ -88,9 +96,12 @@ class ReviewersViewModelTest { Reviewer(3, "user3", false, 7, null) ) } + verifyOrder { - eventTracker.trackRefreshData() - eventTracker.trackRefreshDataSuccess() + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(RefreshDataEvent) + eventTracker.trackEvent(RefreshDataSuccessEvent) } } @@ -117,6 +128,12 @@ class ReviewersViewModelTest { viewModel.refreshData() expectThat(viewModel.isRefreshing.value).isTrue() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(RefreshDataEvent) + } } @Test @@ -126,6 +143,13 @@ class ReviewersViewModelTest { viewModel.refreshData() expectThat(viewModel.isRefreshing.value).isFalse() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(RefreshDataEvent) + eventTracker.trackEvent(RefreshDataSuccessEvent) + } } @Test @@ -143,6 +167,11 @@ class ReviewersViewModelTest { verify(exactly = 1) { savedStateHandle.get("repo") } verify(exactly = 1) { savedStateHandle.get("pull_request_number") } verify(exactly = 1) { savedStateHandle.get("submission_date") } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -152,6 +181,11 @@ class ReviewersViewModelTest { expectThat(viewModel.state) .get(ReviewersState::pullRequestNumber) .isEqualTo("correctPullRequestNumber") + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -166,6 +200,10 @@ class ReviewersViewModelTest { viewModel = createViewModel() expectThat(viewModel.state.data).isA() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + } } @Test @@ -190,6 +228,11 @@ class ReviewersViewModelTest { expectThat(viewModel.state.data).isA().and { get(Data.Success::reviewers).isEmpty() } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -204,6 +247,11 @@ class ReviewersViewModelTest { Reviewer(3, "user3", false, 7, null) ) } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -221,6 +269,11 @@ class ReviewersViewModelTest { Reviewer(3, "user3", false, 7, null) ) } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -240,6 +293,11 @@ class ReviewersViewModelTest { Reviewer(1, "user1", true, 7, 5) ) } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -258,6 +316,11 @@ class ReviewersViewModelTest { viewModel = createViewModel() expectThat(viewModel.state.data).isA() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataFailureEvent) + } } @Test @@ -273,6 +336,11 @@ class ReviewersViewModelTest { viewModel = createViewModel() expectThat(viewModel.state.data).isA() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataFailureEvent) + } } @Test @@ -284,6 +352,11 @@ class ReviewersViewModelTest { viewModel = createViewModel() expectThat(viewModel.state.data).isA() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataFailureEvent) + } } } @@ -299,6 +372,13 @@ class ReviewersViewModelTest { expectThat(viewModel.state) .get(ReviewersState::snackbarTypeShown) .isEqualTo(ReviewersSnackbarType.SUCCESS) + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(ClickNotifyEvent) + eventTracker.trackEvent(NotifySuccessEvent) + } } @Test @@ -327,6 +407,12 @@ class ReviewersViewModelTest { .filterNot { it.login == "user1" } .all { get(Reviewer::isLoading).isFalse() } } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(ClickNotifyEvent) + } } @Test @@ -343,6 +429,13 @@ class ReviewersViewModelTest { expectThat(viewModel.state) .get(ReviewersState::snackbarTypeShown) .isEqualTo(ReviewersSnackbarType.FAILURE) + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(ClickNotifyEvent) + eventTracker.trackEvent(NotifyFailureEvent) + } } @Test @@ -356,6 +449,13 @@ class ReviewersViewModelTest { expectThat(viewModel.state) .get(ReviewersState::snackbarTypeShown) .isNull() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + eventTracker.trackEvent(ClickNotifyEvent) + eventTracker.trackEvent(NotifySuccessEvent) + } } @Test @@ -379,6 +479,13 @@ class ReviewersViewModelTest { Reviewer(3, "user3", false, 7, null) ) } + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataFailureEvent) + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataSuccessEvent) + } } @Test @@ -395,6 +502,13 @@ class ReviewersViewModelTest { viewModel.onAction(ReviewersAction.OnTryAgain) expectThat(viewModel.state.data).isA() + + verifyOrder { + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataFailureEvent) + eventTracker.trackEvent(FetchDataEvent) + eventTracker.trackEvent(FetchDataFailureEvent) + } } } } From ca52ba4d836853002f16fccb8e1f318c7f7ea98c Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Tue, 31 Oct 2023 14:31:41 +0100 Subject: [PATCH 11/18] Add tests for converter, tracker and viewModel. --- .../analytics/EventParametersConverterTest.kt | 55 ++++++++++++++++++ .../FirebaseAnalyticsEventTrackerTest.kt | 56 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt create mode 100644 app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt diff --git a/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt b/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt new file mode 100644 index 000000000..4efef4483 --- /dev/null +++ b/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import strikt.api.expectThat +import strikt.assertions.isEqualTo + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28]) // Use the desired Android SDK version. +class EventParametersConverterTest { + + companion object { + private const val PARAM_NAME1 = "param_name1" + private const val PARAM_NAME2 = "param_name2" + private const val PARAM_NAME3 = "param_name3" + private const val STRING_VALUE1 = "string_value1" + private const val STRING_VALUE2 = "string_value2" + private const val BOOLEAN_VALUE = true + } + + private val exampleParameters: List> = listOf( + StringEventParameter(PARAM_NAME1, STRING_VALUE1), + StringEventParameter(PARAM_NAME2, STRING_VALUE2), + BooleanEventParameter(PARAM_NAME3, BOOLEAN_VALUE) + ) + + private val converter = EventParametersConverter() + + @Test + fun testConvert() { + val result = converter.convert(exampleParameters) + + expectThat(result.getString(PARAM_NAME1)).isEqualTo(STRING_VALUE1) + expectThat(result.getString(PARAM_NAME2)).isEqualTo(STRING_VALUE2) + expectThat(result.getBoolean(PARAM_NAME3)).isEqualTo(BOOLEAN_VALUE) + } +} diff --git a/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt b/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt new file mode 100644 index 000000000..b74d8d090 --- /dev/null +++ b/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2023 AppUnite S.A. + * + * 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.appunite.loudius.analytics + +import com.google.firebase.analytics.FirebaseAnalytics +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28]) // Use the desired Android SDK version. +class FirebaseAnalyticsEventTrackerTest { + + private val firebaseAnalytics: FirebaseAnalytics = mockk() + private val converter = EventParametersConverter() + private val eventTracker = FirebaseAnalyticsEventTracker(firebaseAnalytics, converter) + + private val event = object : Event { + override val name: String = "sample_event" + override val parameters: List> = emptyList() + } + + @Before + fun setUp() { + mockkStatic(FirebaseAnalytics::class) + every { FirebaseAnalytics.getInstance(any()) } returns firebaseAnalytics + every { firebaseAnalytics.logEvent(any(), any()) } returns Unit + } + + @Test + fun testTrackEvent() { + eventTracker.trackEvent(event) + + verify { firebaseAnalytics.logEvent(any(), any()) } + } +} From 255a58e2e3c5d6abf84f2c4c454319add7ac2508 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Tue, 14 Nov 2023 09:08:14 +0100 Subject: [PATCH 12/18] Code cleanup. --- .github/workflows/run-test.yml | 10 +-- .../com/appunite/loudius/analytics/Event.kt | 2 +- .../loudius/analytics/EventParameter.kt | 18 ++--- .../analytics/EventParametersConverter.kt | 6 +- .../analytics/events/ReviewersEvents.kt | 74 +++++++++---------- .../loudius/analytics/util/EventsConstants.kt | 23 ------ 6 files changed, 46 insertions(+), 87 deletions(-) delete mode 100644 app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index ff22444b9..9b62a5db6 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -32,13 +32,10 @@ jobs: with: lfs: true - - name: Create file - run: cat /home/runner/work/Loudius/Loudius/app/google-services.json | base64 - - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $GOOGLE_SERVICES_JSON > /home/runner/work/Loudius/Loudius/app/google-services.json + run: echo $GOOGLE_SERVICES_JSON > app/google-services.json - name: Prepare Android Environment uses: ./.github/actions/prepare-android-env @@ -72,13 +69,10 @@ jobs: with: lfs: true - - name: Create file - run: cat /home/runner/work/Loudius/Loudius/app/google-services.json | base64 - - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $GOOGLE_SERVICES_JSON > /home/runner/work/Loudius/Loudius/app/google-services.json + run: echo $GOOGLE_SERVICES_JSON > app/google-services.json - name: LFS-warning - Prevent large files that are not LFS tracked uses: ppremk/lfs-warning@v3.2 diff --git a/app/src/main/java/com/appunite/loudius/analytics/Event.kt b/app/src/main/java/com/appunite/loudius/analytics/Event.kt index b325af9ed..9c23e8396 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/Event.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/Event.kt @@ -18,5 +18,5 @@ package com.appunite.loudius.analytics interface Event { val name: String - val parameters: List> + val parameters: List } diff --git a/app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt b/app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt index 32f87ffbb..96f7a2ec6 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/EventParameter.kt @@ -16,17 +16,9 @@ package com.appunite.loudius.analytics -sealed class EventParameter { - abstract val name: String - abstract val value: T -} - -data class StringEventParameter( - override val name: String, - override val value: String -) : EventParameter() +sealed class EventParameter { + abstract val name: kotlin.String -data class BooleanEventParameter( - override val name: String, - override val value: Boolean -) : EventParameter() + data class String(override val name: kotlin.String, val value: kotlin.String) : EventParameter() + data class Boolean(override val name: kotlin.String, val value: kotlin.Boolean) : EventParameter() +} diff --git a/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt b/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt index dcce0b8ce..67ccc1654 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/EventParametersConverter.kt @@ -20,12 +20,12 @@ import android.os.Bundle class EventParametersConverter { - fun convert(parameters: List>): Bundle { + fun convert(parameters: List): Bundle { val bundle = Bundle() for (parameter in parameters) { when (parameter) { - is StringEventParameter -> bundle.putString(parameter.name, parameter.value) - is BooleanEventParameter -> bundle.putBoolean(parameter.name, parameter.value) + is EventParameter.String -> bundle.putString(parameter.name, parameter.value) + is EventParameter.Boolean -> bundle.putBoolean(parameter.name, parameter.value) } } return bundle diff --git a/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt b/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt index 5d796bc9b..a8dec90fc 100644 --- a/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt +++ b/app/src/main/java/com/appunite/loudius/analytics/events/ReviewersEvents.kt @@ -16,80 +16,76 @@ package com.appunite.loudius.analytics.events -import com.appunite.loudius.analytics.BooleanEventParameter import com.appunite.loudius.analytics.Event import com.appunite.loudius.analytics.EventParameter -import com.appunite.loudius.analytics.StringEventParameter -import com.appunite.loudius.analytics.util.EventsConstants.ITEM_NAME -import com.appunite.loudius.analytics.util.EventsConstants.SUCCESS -interface ReviewersEvent: Event +interface ReviewersEvent : Event -object ClickNotifyEvent: ReviewersEvent { +object ClickNotifyEvent : ReviewersEvent { override val name: String = "button_click" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "notify") + override val parameters: List = listOf( + EventParameter.String("item_name", "notify") ) } -object NotifySuccessEvent: ReviewersEvent { +object NotifySuccessEvent : ReviewersEvent { override val name: String = "action_finished" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "notify"), - BooleanEventParameter(SUCCESS, true) - ) + override val parameters: List = listOf( + EventParameter.String("item_name", "notify"), + EventParameter.Boolean("success", true) + ) } -object NotifyFailureEvent: ReviewersEvent { +object NotifyFailureEvent : ReviewersEvent { override val name: String = "action_finished" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "notify"), - BooleanEventParameter(SUCCESS, false) + override val parameters: List = listOf( + EventParameter.String("item_name", "notify"), + EventParameter.Boolean("success", false) ) } -object RefreshDataEvent: ReviewersEvent { +object RefreshDataEvent : ReviewersEvent { override val name: String = "action_start" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "refresh_reviewers_data") + override val parameters: List = listOf( + EventParameter.String("item_name", "refresh_reviewers_data") ) } -object RefreshDataSuccessEvent: ReviewersEvent { +object RefreshDataSuccessEvent : ReviewersEvent { override val name: String = "action_finished" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "refresh_reviewers_data"), - BooleanEventParameter(SUCCESS, true) + override val parameters: List = listOf( + EventParameter.String("item_name", "refresh_reviewers_data"), + EventParameter.Boolean("success", true) ) } -object RefreshDataFailureEvent: ReviewersEvent { +object RefreshDataFailureEvent : ReviewersEvent { override val name: String = "action_finished" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "refresh_reviewers_data"), - BooleanEventParameter(SUCCESS, false) + override val parameters: List = listOf( + EventParameter.String("item_name", "refresh_reviewers_data"), + EventParameter.Boolean("success", false) ) } -object FetchDataEvent: ReviewersEvent { +object FetchDataEvent : ReviewersEvent { override val name: String = "action_start" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "fetch_reviewers_data") + override val parameters: List = listOf( + EventParameter.String("item_name", "fetch_reviewers_data") ) } -object FetchDataSuccessEvent: ReviewersEvent { +object FetchDataSuccessEvent : ReviewersEvent { override val name: String = "action_finished" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "fetch_reviewers_data"), - BooleanEventParameter(SUCCESS, true) + override val parameters: List = listOf( + EventParameter.String("item_name", "fetch_reviewers_data"), + EventParameter.Boolean("success", true) ) } -object FetchDataFailureEvent: ReviewersEvent { +object FetchDataFailureEvent : ReviewersEvent { override val name: String = "action_finished" - override val parameters: List> = listOf( - StringEventParameter(ITEM_NAME, "fetch_reviewers_data"), - BooleanEventParameter(SUCCESS, false) + override val parameters: List = listOf( + EventParameter.String("item_name", "fetch_reviewers_data"), + EventParameter.Boolean("success", false) ) } diff --git a/app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt b/app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt deleted file mode 100644 index 643b04fc7..000000000 --- a/app/src/main/java/com/appunite/loudius/analytics/util/EventsConstants.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2023 AppUnite S.A. - * - * 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.appunite.loudius.analytics.util - -object EventsConstants { - - const val ITEM_NAME = "item_name" - const val SUCCESS = "success" -} From 01692fbbba20359780799bb2f974b533ed1f04ce Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Tue, 14 Nov 2023 11:38:54 +0100 Subject: [PATCH 13/18] Add check if google-services.json file exists. --- app/build.gradle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 3f24663be..f063ff807 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,11 +2,16 @@ plugins { id 'com.android.application' id 'kotlin-kapt' id 'org.jetbrains.kotlin.android' - id 'com.google.gms.google-services' id 'org.jlleitschuh.gradle.ktlint' version '11.6.1' id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.0' } +def fileName = "../app/google-services.json" + +if (project.file(fileName).exists()) { + apply plugin: 'com.google.gms.google-services' +} + java { toolchain { languageVersion = JavaLanguageVersion.of(17) From f6d1f13ab4dc61ba79c9cae3b4c4f1088020e43e Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Tue, 14 Nov 2023 14:10:46 +0100 Subject: [PATCH 14/18] Add github action to create json. --- .github/workflows/run-test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 9b62a5db6..4e0697d82 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -32,6 +32,13 @@ jobs: with: lfs: true + - name: create-json + id: create-json + uses: jsdaniell/create-json@v1.2.2 + with: + name: "app/google-services.json" + json: ${{ secrets.GOOGLE_SERVICES_JSON }} + - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} From 181343cd5e2ccf127602dc946a650022f2125fb8 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Thu, 16 Nov 2023 09:18:49 +0100 Subject: [PATCH 15/18] Add double quote to $GOOGLE_SERVICES_JSON. --- .github/workflows/run-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 4e0697d82..27d241841 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -42,7 +42,7 @@ jobs: - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $GOOGLE_SERVICES_JSON > app/google-services.json + run: echo "$GOOGLE_SERVICES_JSON" > app/google-services.json - name: Prepare Android Environment uses: ./.github/actions/prepare-android-env @@ -79,7 +79,7 @@ jobs: - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $GOOGLE_SERVICES_JSON > app/google-services.json + run: echo "$GOOGLE_SERVICES_JSON" > app/google-services.json - name: LFS-warning - Prevent large files that are not LFS tracked uses: ppremk/lfs-warning@v3.2 From b26923e6a8ea6bae0d97801aa05a908853b2471b Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Thu, 16 Nov 2023 10:14:57 +0100 Subject: [PATCH 16/18] Adjust tests. --- .../loudius/analytics/EventParametersConverterTest.kt | 8 ++++---- .../analytics/FirebaseAnalyticsEventTrackerTest.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt b/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt index 4efef4483..9b26f6685 100644 --- a/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt +++ b/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt @@ -36,10 +36,10 @@ class EventParametersConverterTest { private const val BOOLEAN_VALUE = true } - private val exampleParameters: List> = listOf( - StringEventParameter(PARAM_NAME1, STRING_VALUE1), - StringEventParameter(PARAM_NAME2, STRING_VALUE2), - BooleanEventParameter(PARAM_NAME3, BOOLEAN_VALUE) + private val exampleParameters: List = listOf( + EventParameter.String(PARAM_NAME1, STRING_VALUE1), + EventParameter.String(PARAM_NAME2, STRING_VALUE2), + EventParameter.Boolean(PARAM_NAME3, BOOLEAN_VALUE) ) private val converter = EventParametersConverter() diff --git a/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt b/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt index b74d8d090..afbaf58fb 100644 --- a/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt +++ b/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt @@ -37,7 +37,7 @@ class FirebaseAnalyticsEventTrackerTest { private val event = object : Event { override val name: String = "sample_event" - override val parameters: List> = emptyList() + override val parameters: List = emptyList() } @Before From ddda2a070d69a793cafd7d1d7f51d8478ffdacbc Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Thu, 16 Nov 2023 11:00:40 +0100 Subject: [PATCH 17/18] Add base64 decoding. --- .github/workflows/run-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 27d241841..6f196fe3e 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -42,7 +42,7 @@ jobs: - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo "$GOOGLE_SERVICES_JSON" > app/google-services.json + run: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json - name: Prepare Android Environment uses: ./.github/actions/prepare-android-env @@ -79,7 +79,7 @@ jobs: - name: Add google-services.json env: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo "$GOOGLE_SERVICES_JSON" > app/google-services.json + run: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json - name: LFS-warning - Prevent large files that are not LFS tracked uses: ppremk/lfs-warning@v3.2 From 71d40c64eae79e67f2c94a12b942c1f307c53a43 Mon Sep 17 00:00:00 2001 From: nowakweronika Date: Tue, 21 Nov 2023 13:34:14 +0100 Subject: [PATCH 18/18] Code cleanup. --- .../analytics/EventParametersConverterTest.kt | 31 +++++++------------ .../FirebaseAnalyticsEventTrackerTest.kt | 3 -- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt b/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt index 9b26f6685..07f61327d 100644 --- a/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt +++ b/app/src/test/java/com/appunite/loudius/analytics/EventParametersConverterTest.kt @@ -27,29 +27,20 @@ import strikt.assertions.isEqualTo @Config(sdk = [28]) // Use the desired Android SDK version. class EventParametersConverterTest { - companion object { - private const val PARAM_NAME1 = "param_name1" - private const val PARAM_NAME2 = "param_name2" - private const val PARAM_NAME3 = "param_name3" - private const val STRING_VALUE1 = "string_value1" - private const val STRING_VALUE2 = "string_value2" - private const val BOOLEAN_VALUE = true - } - - private val exampleParameters: List = listOf( - EventParameter.String(PARAM_NAME1, STRING_VALUE1), - EventParameter.String(PARAM_NAME2, STRING_VALUE2), - EventParameter.Boolean(PARAM_NAME3, BOOLEAN_VALUE) - ) - private val converter = EventParametersConverter() @Test fun testConvert() { - val result = converter.convert(exampleParameters) - - expectThat(result.getString(PARAM_NAME1)).isEqualTo(STRING_VALUE1) - expectThat(result.getString(PARAM_NAME2)).isEqualTo(STRING_VALUE2) - expectThat(result.getBoolean(PARAM_NAME3)).isEqualTo(BOOLEAN_VALUE) + val result = converter.convert( + listOf( + EventParameter.String("param_name1", "string_value1"), + EventParameter.String("param_name2", "string_value2"), + EventParameter.Boolean("param_name3", true) + ) + ) + + expectThat(result.getString("param_name1")).isEqualTo("string_value1") + expectThat(result.getString("param_name2")).isEqualTo("string_value2") + expectThat(result.getBoolean("param_name3")).isEqualTo(true) } } diff --git a/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt b/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt index afbaf58fb..0520a9b36 100644 --- a/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt +++ b/app/src/test/java/com/appunite/loudius/analytics/FirebaseAnalyticsEventTrackerTest.kt @@ -23,11 +23,8 @@ import io.mockk.mockkStatic import io.mockk.verify import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -@RunWith(RobolectricTestRunner::class) @Config(sdk = [28]) // Use the desired Android SDK version. class FirebaseAnalyticsEventTrackerTest {