Skip to content

Bump posthog version to 3.2.0 #8820

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8820.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update posthog sdk to 3.2.0
2 changes: 1 addition & 1 deletion dependencies_groups.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ ext.groups = [
'com.parse.bolts',
'com.pinterest',
'com.pinterest.ktlint',
'com.posthog.android',
'com.posthog',
'com.squareup',
'com.squareup.curtains',
'com.squareup.duktape',
Expand Down
4 changes: 1 addition & 3 deletions vector/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,7 @@ dependencies {
kapt libs.dagger.hiltCompiler

// Analytics
implementation('com.posthog.android:posthog:2.0.3') {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation 'com.posthog:posthog-android:3.2.0'
implementation libs.sentry.sentryAndroid

// UnifiedPush
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

package im.vector.app.features.analytics.impl

import com.posthog.android.Options
import com.posthog.android.PostHog
import com.posthog.android.Properties
import com.posthog.PostHogInterface
import im.vector.app.core.di.NamedGlobalScope
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.VectorAnalytics
Expand All @@ -36,9 +34,6 @@ import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

private val REUSE_EXISTING_ID: String? = null
private val IGNORED_OPTIONS: Options? = null

@Singleton
class DefaultVectorAnalytics @Inject constructor(
private val postHogFactory: PostHogFactory,
Expand All @@ -49,9 +44,9 @@ class DefaultVectorAnalytics @Inject constructor(
@NamedGlobalScope private val globalScope: CoroutineScope
) : VectorAnalytics {

private var posthog: PostHog? = null
private var posthog: PostHogInterface? = null

private fun createPosthog(): PostHog? {
private fun createPosthog(): PostHogInterface? {
return when {
analyticsConfig.isEnabled -> postHogFactory.createPosthog()
else -> {
Expand Down Expand Up @@ -126,7 +121,7 @@ class DefaultVectorAnalytics @Inject constructor(
posthog?.reset()
} else {
Timber.tag(analyticsTag.value).d("identify")
posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS)
posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties())
}
}

Expand Down Expand Up @@ -155,16 +150,16 @@ class DefaultVectorAnalytics @Inject constructor(
when (_userConsent) {
true -> {
posthog = createPosthog()
posthog?.optOut(false)
posthog?.optIn()
identifyPostHog()
pendingUserProperties?.let { doUpdateUserProperties(it) }
pendingUserProperties = null
}
false -> {
// When opting out, ensure that the queue is flushed first, or it will be flushed later (after user has revoked consent)
posthog?.flush()
posthog?.optOut(true)
posthog?.shutdown()
posthog?.optOut()
posthog?.close()
posthog = null
}
}
Expand All @@ -177,6 +172,7 @@ class DefaultVectorAnalytics @Inject constructor(
?.takeIf { userConsent == true }
?.capture(
event.getName(),
analyticsId,
event.getProperties()?.toPostHogProperties()
)
}
Expand All @@ -197,27 +193,37 @@ class DefaultVectorAnalytics @Inject constructor(
}

private fun doUpdateUserProperties(userProperties: UserProperties) {
// we need a distinct id to set user properties
val distinctId = analyticsId ?: return
posthog
?.takeIf { userConsent == true }
?.identify(REUSE_EXISTING_ID, userProperties.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS)
?.identify(distinctId, userProperties.getProperties())
}

private fun Map<String, Any?>?.toPostHogProperties(): Properties? {
private fun Map<String, Any?>?.toPostHogProperties(): Map<String, Any>? {
if (this == null) return null

return Properties().apply {
putAll(this@toPostHogProperties)
val nonNulls = HashMap<String, Any>()
this.forEach { (key, value) ->
if (value != null) {
nonNulls[key] = value
}
}
return nonNulls
}

/**
* We avoid sending nulls as part of the UserProperties as this will reset the values across all devices.
* The UserProperties event has nullable properties to allow for clients to opt in.
*/
private fun Map<String, Any?>.toPostHogUserProperties(): Properties {
return Properties().apply {
putAll([email protected] { it.value != null })
private fun Map<String, Any?>.toPostHogUserProperties(): Map<String, Any> {
val nonNulls = HashMap<String, Any>()
this.forEach { (key, value) ->
if (value != null) {
nonNulls[key] = value
}
}
return nonNulls
}

override fun trackError(throwable: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package im.vector.app.features.analytics.impl

import android.content.Context
import com.posthog.android.PostHog
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroid
import com.posthog.android.PostHogAndroidConfig
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.analytics.AnalyticsConfig
import javax.inject.Inject
Expand All @@ -28,29 +30,17 @@ class PostHogFactory @Inject constructor(
private val buildMeta: BuildMeta,
) {

fun createPosthog(): PostHog {
return PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost)
// Record certain application events automatically! (off/false by default)
// .captureApplicationLifecycleEvents()
// Record screen views automatically! (off/false by default)
// .recordScreenViews()
// Capture deep links as part of the screen call. (off by default)
// .captureDeepLinks()
// Maximum number of events to keep in queue before flushing (default 20)
// .flushQueueSize(20)
// Max delay before flushing the queue (30 seconds)
// .flushInterval(30, TimeUnit.SECONDS)
// Enable or disable collection of ANDROID_ID (true)
.collectDeviceId(false)
.logLevel(getLogLevel())
.build()
}

private fun getLogLevel(): PostHog.LogLevel {
return if (buildMeta.isDebug) {
PostHog.LogLevel.DEBUG
} else {
PostHog.LogLevel.INFO
fun createPosthog(): PostHogInterface {
val config = PostHogAndroidConfig(
apiKey = analyticsConfig.postHogApiKey,
host = analyticsConfig.postHogHost,
// we do that manually
captureScreenViews = false,
).also {
if (buildMeta.isDebug) {
it.debug = true
}
}
return PostHogAndroid.with(context, config)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

package im.vector.app.features.analytics.impl

import com.posthog.android.Properties
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.test.fakes.FakeAnalyticsStore
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
import im.vector.app.test.fakes.FakePostHog
Expand Down Expand Up @@ -128,7 +125,7 @@ class DefaultVectorAnalyticsTest {

defaultVectorAnalytics.screen(A_SCREEN_EVENT)

fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), A_SCREEN_EVENT.toPostHogProperties())
fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), A_SCREEN_EVENT.getProperties())
}

@Test
Expand All @@ -146,7 +143,7 @@ class DefaultVectorAnalyticsTest {

defaultVectorAnalytics.capture(AN_EVENT)

fakePostHog.verifyEventTracked(AN_EVENT.getName(), AN_EVENT.toPostHogProperties())
fakePostHog.verifyEventTracked(AN_EVENT.getName(), AN_EVENT.getProperties().clearNulls())
}

@Test
Expand Down Expand Up @@ -176,16 +173,16 @@ class DefaultVectorAnalyticsTest {

fakeSentryAnalytics.verifyNoErrorTracking()
}
}

private fun VectorAnalyticsScreen.toPostHogProperties(): Properties? {
return getProperties()?.let { properties ->
Properties().also { it.putAll(properties) }
}
}
private fun Map<String, Any?>?.clearNulls(): Map<String, Any>? {
if (this == null) return null

private fun VectorAnalyticsEvent.toPostHogProperties(): Properties? {
return getProperties()?.let { properties ->
Properties().also { it.putAll(properties) }
val nonNulls = HashMap<String, Any>()
this.forEach { (key, value) ->
if (value != null) {
nonNulls[key] = value
}
}
return nonNulls
}
}
19 changes: 10 additions & 9 deletions vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package im.vector.app.test.fakes

import android.os.Looper
import com.posthog.android.PostHog
import com.posthog.android.Properties
import com.posthog.PostHogInterface
import im.vector.app.features.analytics.plan.UserProperties
import io.mockk.every
import io.mockk.mockk
Expand All @@ -36,16 +35,19 @@ class FakePostHog {
every { Looper.getMainLooper() } returns looper
}

val instance = mockk<PostHog>(relaxed = true)
val instance = mockk<PostHogInterface>(relaxed = true)

fun verifyOptOutStatus(optedOut: Boolean) {
verify { instance.optOut(optedOut) }
if (optedOut) {
verify { instance.optOut() }
} else {
verify { instance.optIn() }
}
}

fun verifyIdentifies(analyticsId: String, userProperties: UserProperties?) {
verify {
val postHogProperties = userProperties?.getProperties()
?.let { rawProperties -> Properties().also { it.putAll(rawProperties) } }
?.takeIf { it.isNotEmpty() }
instance.identify(analyticsId, postHogProperties, null)
}
Expand All @@ -55,20 +57,19 @@ class FakePostHog {
verify { instance.reset() }
}

fun verifyScreenTracked(name: String, properties: Properties?) {
fun verifyScreenTracked(name: String, properties: Map<String, Any>?) {
verify { instance.screen(name, properties) }
}

fun verifyNoScreenTracking() {
verify(exactly = 0) {
instance.screen(any())
instance.screen(any(), any())
instance.screen(any(), any(), any())
}
}

fun verifyEventTracked(name: String, properties: Properties?) {
verify { instance.capture(name, properties) }
fun verifyEventTracked(name: String, properties: Map<String, Any>?) {
verify { instance.capture(name, null, properties) }
}

fun verifyNoEventTracking() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@

package im.vector.app.test.fakes

import com.posthog.android.PostHog
import com.posthog.PostHogInterface
import im.vector.app.features.analytics.impl.PostHogFactory
import io.mockk.every
import io.mockk.mockk

class FakePostHogFactory(postHog: PostHog) {
class FakePostHogFactory(postHog: PostHogInterface) {
val instance = mockk<PostHogFactory>().also {
every { it.createPosthog() } returns postHog
}
Expand Down
Loading