Skip to content

Commit

Permalink
MBL-1236 Call CreateAttributionEvent on ProjectPage for project page … (
Browse files Browse the repository at this point in the history
#1972)

* MBL-1236 Call CreateAttributionEvent on ProjectPage for project page viewed event

* No need to manually add props that backend will send for free

* Small edits

* Fix lint

* Fix tests

---------

Co-authored-by: mtgriego <[email protected]>
  • Loading branch information
ycheng-kickstarter and mtgriego authored Mar 11, 2024
1 parent 484f230 commit b220df4
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 11 deletions.
10 changes: 10 additions & 0 deletions app/src/main/java/com/kickstarter/ApplicationModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.kickstarter.libs.ApiEndpoint;
import com.kickstarter.libs.AttributionEvents;
import com.kickstarter.libs.Build;
import com.kickstarter.libs.CurrentConfig;
import com.kickstarter.libs.CurrentConfigV2;
Expand Down Expand Up @@ -136,6 +137,7 @@ static Environment provideEnvironment(final @NonNull @ActivitySamplePreference I
final @NonNull KSCurrency ksCurrency,
final @NonNull KSString ksString,
final @NonNull AnalyticEvents analytics,
final @NonNull AttributionEvents attributionEvents,
final @NonNull Logout logout,
final @NonNull PlayServicesCapability playServicesCapability,
final @NonNull Scheduler scheduler,
Expand Down Expand Up @@ -166,6 +168,7 @@ static Environment provideEnvironment(final @NonNull @ActivitySamplePreference I
.ksCurrency(ksCurrency)
.ksString(ksString)
.analytics(analytics)
.attributionEvents(attributionEvents)
.logout(logout)
.playServicesCapability(playServicesCapability)
.scheduler(scheduler)
Expand Down Expand Up @@ -443,6 +446,13 @@ static AnalyticEvents provideAnalytics(
return new AnalyticEvents(clients);
}

@Provides
@Singleton
static AttributionEvents provideAttributionEvents(
final @NonNull ApolloClientTypeV2 apolloClient) {
return new AttributionEvents(apolloClient);
}

@Provides
@Singleton
static Scheduler provideScheduler() {
Expand Down
52 changes: 52 additions & 0 deletions app/src/main/java/com/kickstarter/libs/AttributionEvents.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.kickstarter.libs

import android.annotation.SuppressLint
import com.kickstarter.libs.utils.AnalyticEventsUtils
import com.kickstarter.libs.utils.ContextPropertyKeyName
import com.kickstarter.libs.utils.EventContextValues
import com.kickstarter.libs.utils.EventName
import com.kickstarter.models.Project
import com.kickstarter.services.ApolloClientTypeV2
import com.kickstarter.services.mutations.CreateAttributionEventData
import com.kickstarter.services.transformers.encodeRelayId
import com.kickstarter.ui.data.ProjectData
import timber.log.Timber

/**
* Similar to the [AnalyticEvents] class but specifically for sending attribution events to KSR
* event attribution backend.
*
*/
class AttributionEvents(
val apolloClient: ApolloClientTypeV2
) {
/**
* Sends data to the backend for event attribution when the projects screen is loaded.
*
* @param projectData: The selected project data.
*/
fun trackProjectPageViewed(projectData: ProjectData) {
val props: HashMap<String, Any> = hashMapOf(ContextPropertyKeyName.CONTEXT_PAGE.contextName to EventContextValues.ContextPageName.PROJECT.contextName)
props["context_page_url"] = projectData.fullDeeplink()?.toString() ?: ""
props.putAll(AnalyticEventsUtils.refTagProperties(projectData.refTagFromIntent(), projectData.refTagFromCookie()))
track(EventName.PROJECT_PAGE_VIEWED.eventName, props, projectData.project())
}

@SuppressLint("CheckResult")
private fun track(eventName: String, eventProperties: Map<String, Any?>, project: Project) {
val attributionEventData = CreateAttributionEventData(
eventName = eventName,
eventProperties = eventProperties,
projectId = encodeRelayId(project)
)
createAttributionEvent(attributionEventData)
.subscribe {
Timber.tag("Event Attribution").d("Project page viewed sent to backend: %s", it)
}
}

private fun createAttributionEvent(eventInput: CreateAttributionEventData) =
this.apolloClient.createAttributionEvent(eventInput)
.materialize()
.share()
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/kickstarter/libs/Environment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Environment private constructor(
private val ksCurrency: KSCurrency?,
private val ksString: KSString?,
private val analytics: AnalyticEvents?,
private val attributionEvents: AttributionEvents?,
private val logout: Logout?,
private val playServicesCapability: PlayServicesCapability?,
private val scheduler: Scheduler?,
Expand Down Expand Up @@ -63,6 +64,7 @@ class Environment private constructor(
fun ksCurrency() = this.ksCurrency
fun ksString() = this.ksString
fun analytics() = this.analytics
fun attributionEvents() = this.attributionEvents
fun logout() = this.logout
fun playServicesCapability() = this.playServicesCapability
fun scheduler() = this.scheduler
Expand Down Expand Up @@ -93,6 +95,7 @@ class Environment private constructor(
private var ksCurrency: KSCurrency? = null,
private var ksString: KSString? = null,
private var analytics: AnalyticEvents? = null,
private var attributionEvents: AttributionEvents? = null,
private var logout: Logout? = null,
private var playServicesCapability: PlayServicesCapability? = null,
private var scheduler: Scheduler? = null,
Expand Down Expand Up @@ -122,6 +125,7 @@ class Environment private constructor(
fun ksCurrency(ksCurrency: KSCurrency) = apply { this.ksCurrency = ksCurrency }
fun ksString(ksString: KSString) = apply { this.ksString = ksString }
fun analytics(analytics: AnalyticEvents) = apply { this.analytics = analytics }
fun attributionEvents(attributionEvents: AttributionEvents) = apply { this.attributionEvents = attributionEvents }
fun logout(logout: Logout) = apply { this.logout = logout }
fun playServicesCapability(playServicesCapability: PlayServicesCapability) = apply { this.playServicesCapability = playServicesCapability }
fun scheduler(scheduler: Scheduler) = apply { this.scheduler = scheduler }
Expand Down Expand Up @@ -153,6 +157,7 @@ class Environment private constructor(
ksCurrency = ksCurrency,
ksString = ksString,
analytics = analytics,
attributionEvents = attributionEvents,
logout = logout,
playServicesCapability = playServicesCapability,
scheduler = scheduler,
Expand Down Expand Up @@ -185,6 +190,7 @@ class Environment private constructor(
ksCurrency = ksCurrency,
ksString = ksString,
analytics = analytics,
attributionEvents = attributionEvents,
logout = logout,
playServicesCapability = playServicesCapability,
scheduler = scheduler,
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/com/kickstarter/libs/utils/EventName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ package com.kickstarter.libs.utils
* PAGE_VIEWED: Event when a screen is loaded.
*/
enum class EventName(val eventName: String) {
// Analytic events
CTA_CLICKED("CTA Clicked"),
CARD_CLICKED("Card Clicked"),
PAGE_VIEWED("Page Viewed"),
VIDEO_PLAYBACK_STARTED("Video Playback Started"),
VIDEO_PLAYBACK_COMPLETED("Video Playback Completed")
VIDEO_PLAYBACK_COMPLETED("Video Playback Completed"),

// Attribution events
PROJECT_PAGE_VIEWED("Project Page Viewed")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package com.kickstarter.services.mutations
*/
data class CreateAttributionEventData(
val eventName: String,
val eventProperties: Map<String, String>? = null,
val eventProperties: Map<String, Any?>,
val projectId: String? = null,
val clientMutationId: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,6 @@ fun getCreateAttributionEventMutation(eventInput: CreateAttributionEventData, gs
.eventName(eventInput.eventName)
.eventProperties(eventPropertiesJson)
.projectId(eventInput.projectId)
.clientMutationId(eventInput.clientMutationId)
.build()

return CreateAttributionEventMutation.builder().input(graphInput)
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/java/com/kickstarter/ui/data/ProjectData.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.kickstarter.ui.data

import android.net.Uri
import android.os.Parcelable
import com.kickstarter.libs.RefTag
import com.kickstarter.models.Backing
Expand All @@ -8,7 +9,7 @@ import com.kickstarter.models.User
import kotlinx.parcelize.Parcelize

/**
* A light-weight value to hold two ref tags and a project.
* A light-weight value to hold two ref tags, the full deeplink, and a project.
* Two ref tags are stored: one comes from parceled data in the activity
* and the other comes from the ref stored in a cookie associated to the project.
*/
Expand All @@ -17,12 +18,14 @@ import kotlinx.parcelize.Parcelize
class ProjectData private constructor(
private val refTagFromIntent: RefTag?,
private val refTagFromCookie: RefTag?,
private val fullDeeplink: Uri?,
private val project: Project,
private val backing: Backing?,
private val user: User?,
) : Parcelable {
fun refTagFromIntent() = this.refTagFromIntent
fun refTagFromCookie() = this.refTagFromCookie
fun fullDeeplink() = this.fullDeeplink
fun project() = this.project
fun backing() = this.backing
fun user() = this.user
Expand All @@ -31,19 +34,22 @@ class ProjectData private constructor(
data class Builder(
private var refTagFromIntent: RefTag? = null,
private var refTagFromCookie: RefTag? = null,
private var fullDeeplink: Uri? = null,
private var project: Project = Project.builder().build(),
private var backing: Backing? = null,
private var user: User? = null,

) : Parcelable {
fun refTagFromIntent(refTagFromIntent: RefTag?) = apply { this.refTagFromIntent = refTagFromIntent }
fun refTagFromCookie(refTagFromCookie: RefTag?) = apply { this.refTagFromCookie = refTagFromCookie }
fun fullDeeplink(fullDeeplink: Uri?) = apply { this.fullDeeplink = fullDeeplink }
fun project(project: Project) = apply { this.project = project }
fun backing(backing: Backing) = apply { this.backing = backing }
fun user(user: User) = apply { this.user = user }
fun build() = ProjectData(
refTagFromIntent = refTagFromIntent,
refTagFromCookie = refTagFromCookie,
fullDeeplink = fullDeeplink,
project = project,
backing = backing,
user = user
Expand All @@ -53,6 +59,7 @@ class ProjectData private constructor(
fun toBuilder() = Builder(
refTagFromIntent = refTagFromIntent,
refTagFromCookie = refTagFromCookie,
fullDeeplink = fullDeeplink,
project = project,
backing = backing,
user = user
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kickstarter.viewmodels.projectpage

import android.content.Intent
import android.net.Uri
import android.util.Pair
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.ViewModel
Expand Down Expand Up @@ -279,6 +280,7 @@ interface ProjectPageViewModel {
private val currentConfig = requireNotNull(environment.currentConfigV2())
private val featureFlagClient = requireNotNull(environment.featureFlagClient())
private val analyticEvents = requireNotNull(environment.analytics())
private val attributionEvents = requireNotNull(environment.attributionEvents())

private val intent = PublishSubject.create<Intent>()
private val activityResult = BehaviorSubject.create<ActivityResult>()
Expand Down Expand Up @@ -432,6 +434,9 @@ interface ProjectPageViewModel {
val refTag = intent
.flatMap { ProjectIntentMapper.refTag(it) }

val fullDeeplink = intent
.flatMap { Observable.just(KsOptional.of(it.data)) }

val saveProjectFromDeepLinkActivity = intent
.take(1)
.delay(3, TimeUnit.SECONDS, environment.schedulerV2()) // add delay to wait until activity subscribed to viewmodel
Expand Down Expand Up @@ -574,12 +579,13 @@ interface ProjectPageViewModel {
.subscribe { this.showSavedPrompt.onNext(it) }
.addToDisposable(disposables)

val currentProjectData = Observable.combineLatest<KsOptional<RefTag?>, KsOptional<RefTag?>, Project, ProjectData>(
val currentProjectData = Observable.combineLatest<KsOptional<RefTag?>, KsOptional<RefTag?>, KsOptional<Uri?>, Project, ProjectData>(
refTag,
cookieRefTag,
fullDeeplink,
currentProject
) { refTagFromIntent, refTagFromCookie, project ->
projectData(refTagFromIntent, refTagFromCookie, project)
) { refTagFromIntent, refTagFromCookie, fullDeeplink, project ->
projectData(refTagFromIntent, refTagFromCookie, fullDeeplink, project)
}

currentProjectData
Expand Down Expand Up @@ -783,7 +789,7 @@ interface ProjectPageViewModel {
it.first.project().toBuilder().backing(it.second).build()
} else it.first.project()

projectData(KsOptional.of(it.first.refTagFromIntent()), KsOptional.of(it.first.refTagFromCookie()), updatedProject)
projectData(KsOptional.of(it.first.refTagFromIntent()), KsOptional.of(it.first.refTagFromCookie()), KsOptional.of(it.first.fullDeeplink()), updatedProject)
}
.subscribe { this.updateFragments.onNext(it) }
.addToDisposable(disposables)
Expand Down Expand Up @@ -969,7 +975,10 @@ interface ProjectPageViewModel {
}

val dataWithStoredCookieRefTag = storeCurrentCookieRefTag(data)
// Send event to segment
this.analyticEvents.trackProjectScreenViewed(dataWithStoredCookieRefTag, OVERVIEW.contextName)
// Send event to backend event attribution
this.attributionEvents.trackProjectPageViewed(dataWithStoredCookieRefTag)
}.addToDisposable(disposables)

fullProjectDataAndPledgeFlowContext
Expand Down Expand Up @@ -1049,11 +1058,12 @@ interface ProjectPageViewModel {
}
}

private fun projectData(refTagFromIntent: KsOptional<RefTag?>, refTagFromCookie: KsOptional<RefTag?>, project: Project): ProjectData {
private fun projectData(refTagFromIntent: KsOptional<RefTag?>, refTagFromCookie: KsOptional<RefTag?>, fullDeeplink: KsOptional<Uri?>, project: Project): ProjectData {
return ProjectData
.builder()
.refTagFromIntent(refTagFromIntent.getValue())
.refTagFromCookie(refTagFromCookie.getValue())
.fullDeeplink(fullDeeplink.getValue())
.project(project)
.build()
}
Expand Down
7 changes: 5 additions & 2 deletions app/src/test/java/com/kickstarter/KSRobolectricTestCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider
import com.kickstarter.libs.AnalyticEvents
import com.kickstarter.libs.AttributionEvents
import com.kickstarter.libs.Environment
import com.kickstarter.libs.KSCurrency
import com.kickstarter.libs.KSString
Expand Down Expand Up @@ -54,9 +55,10 @@ abstract class KSRobolectricTestCase : TestCase() {
public override fun setUp() {
super.setUp()

val mockApolloClientV2 = MockApolloClientV2()
val mockCurrentConfig = MockCurrentConfig()
val mockCurrentConfigV2 = MockCurrentConfigV2()
val mockFeatureFlagClient: MockFeatureFlagClient = MockFeatureFlagClient()
val mockFeatureFlagClient = MockFeatureFlagClient()
val segmentTestClient = segmentTrackingClient(mockCurrentConfig, mockFeatureFlagClient)

val component = DaggerApplicationComponent.builder()
Expand All @@ -73,11 +75,12 @@ abstract class KSRobolectricTestCase : TestCase() {
.ksCurrency(KSCurrency(mockCurrentConfig))
.apiClient(MockApiClient())
.apolloClient(MockApolloClient())
.apolloClientV2(MockApolloClientV2())
.apolloClientV2(mockApolloClientV2)
.currentConfig(mockCurrentConfig)
.currentConfig2(mockCurrentConfigV2)
.stripe(Stripe(context(), Secrets.StripePublishableKey.STAGING))
.analytics(AnalyticEvents(listOf(segmentTestClient)))
.attributionEvents(AttributionEvents(mockApolloClientV2))
.featureFlagClient(mockFeatureFlagClient)
.build()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class EnvironmentTest : KSRobolectricTestCase() {
assertNotNull(environment.ksCurrency())
assertNotNull(environment.ksString())
assertNotNull(environment.analytics())
assertNotNull(environment.attributionEvents())
assertNotNull(environment.logout())
assertNotNull(environment.playServicesCapability())
assertNotNull(environment.scheduler())
Expand Down

0 comments on commit b220df4

Please sign in to comment.