creating and passing stored user properties on post hog initialisation

- this allows information captured during the onboarding to be sent once the user has opt'd in
This commit is contained in:
Adam Brown 2022-02-15 16:03:21 +00:00
parent 3236d87323
commit d99a2f8d14
6 changed files with 135 additions and 24 deletions

View file

@ -43,6 +43,7 @@ class DefaultVectorAnalytics @Inject constructor(
postHogFactory: PostHogFactory,
analyticsConfig: AnalyticsConfig,
private val analyticsStore: AnalyticsStore,
private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory,
@NamedGlobalScope private val globalScope: CoroutineScope
) : VectorAnalytics {
@ -105,14 +106,14 @@ class DefaultVectorAnalytics @Inject constructor(
.launchIn(globalScope)
}
private fun identifyPostHog() {
private suspend fun identifyPostHog() {
val id = analyticsId ?: return
if (id.isEmpty()) {
Timber.tag(analyticsTag.value).d("reset")
posthog?.reset()
} else {
Timber.tag(analyticsTag.value).d("identify")
posthog?.identify(id)
posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS)
}
}

View file

@ -20,9 +20,13 @@ 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
import im.vector.app.test.fakes.FakePostHogFactory
import im.vector.app.test.fixtures.AnalyticsConfigFixture.anAnalyticsConfig
import im.vector.app.test.fixtures.aUserProperties
import im.vector.app.test.fixtures.aVectorAnalyticsEvent
import im.vector.app.test.fixtures.aVectorAnalyticsScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -31,26 +35,23 @@ import org.junit.Before
import org.junit.Test
private const val AN_ANALYTICS_ID = "analytics-id"
private val A_SCREEN_EVENT = object : VectorAnalyticsScreen {
override fun getName() = "a-screen-event-name"
override fun getProperties() = mapOf("property-name" to "property-value")
}
private val AN_EVENT = object : VectorAnalyticsEvent {
override fun getName() = "an-event-name"
override fun getProperties() = mapOf("property-name" to "property-value")
}
private val A_SCREEN_EVENT = aVectorAnalyticsScreen()
private val AN_EVENT = aVectorAnalyticsEvent()
private val A_LATE_INIT_USER_PROPERTIES = aUserProperties()
@OptIn(ExperimentalCoroutinesApi::class)
class DefaultVectorAnalyticsTest {
private val fakePostHog = FakePostHog()
private val fakeAnalyticsStore = FakeAnalyticsStore()
private val fakeLateInitUserPropertiesFactory = FakeLateInitUserPropertiesFactory()
private val defaultVectorAnalytics = DefaultVectorAnalytics(
postHogFactory = FakePostHogFactory(fakePostHog.instance).instance,
analyticsStore = fakeAnalyticsStore.instance,
globalScope = CoroutineScope(Dispatchers.Unconfined),
analyticsConfig = anAnalyticsConfig(isEnabled = true)
analyticsConfig = anAnalyticsConfig(isEnabled = true),
lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance
)
@Before
@ -87,14 +88,16 @@ class DefaultVectorAnalyticsTest {
}
@Test
fun `when valid analytics id updates then identify`() = runBlockingTest {
fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runBlockingTest {
fakeLateInitUserPropertiesFactory.givenCreatesProperties(A_LATE_INIT_USER_PROPERTIES)
fakeAnalyticsStore.givenAnalyticsId(AN_ANALYTICS_ID)
fakePostHog.verifyIdentifies(AN_ANALYTICS_ID)
fakePostHog.verifyIdentifies(AN_ANALYTICS_ID, A_LATE_INIT_USER_PROPERTIES)
}
@Test
fun `when signing out analytics id updates then resets`() = runBlockingTest {
fun `when signing out then resets posthog`() = runBlockingTest {
fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()
defaultVectorAnalytics.onSignOut()
@ -108,9 +111,7 @@ class DefaultVectorAnalyticsTest {
defaultVectorAnalytics.screen(A_SCREEN_EVENT)
fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), Properties().also {
it.putAll(A_SCREEN_EVENT.getProperties())
})
fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), A_SCREEN_EVENT.toPostHogProperties())
}
@Test
@ -128,9 +129,7 @@ class DefaultVectorAnalyticsTest {
defaultVectorAnalytics.capture(AN_EVENT)
fakePostHog.verifyEventTracked(AN_EVENT.getName(), Properties().also {
it.putAll(AN_EVENT.getProperties())
})
fakePostHog.verifyEventTracked(AN_EVENT.getName(), AN_EVENT.toPostHogProperties())
}
@Test
@ -142,3 +141,15 @@ class DefaultVectorAnalyticsTest {
fakePostHog.verifyNoEventTracking()
}
}
private fun VectorAnalyticsScreen.toPostHogProperties(): Properties? {
return getProperties()?.let { properties ->
Properties().also { it.putAll(properties) }
}
}
private fun VectorAnalyticsEvent.toPostHogProperties(): Properties? {
return getProperties()?.let { properties ->
Properties().also { it.putAll(properties) }
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.test.fakes
import im.vector.app.features.analytics.impl.LateInitUserPropertiesFactory
import im.vector.app.features.analytics.plan.UserProperties
import io.mockk.coEvery
import io.mockk.mockk
class FakeLateInitUserPropertiesFactory {
val instance = mockk<LateInitUserPropertiesFactory>()
fun givenCreatesProperties(userProperties: UserProperties?) {
coEvery { instance.createUserProperties() } returns userProperties
}
}

View file

@ -19,6 +19,7 @@ package im.vector.app.test.fakes
import android.os.Looper
import com.posthog.android.PostHog
import com.posthog.android.Properties
import im.vector.app.features.analytics.plan.UserProperties
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@ -41,15 +42,20 @@ class FakePostHog {
verify { instance.optOut(optedOut) }
}
fun verifyIdentifies(analyticsId: String) {
verify { instance.identify(analyticsId) }
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)
}
}
fun verifyReset() {
verify { instance.reset() }
}
fun verifyScreenTracked(name: String, properties: Properties) {
fun verifyScreenTracked(name: String, properties: Properties?) {
verify { instance.screen(name, properties) }
}
@ -61,7 +67,7 @@ class FakePostHog {
}
}
fun verifyEventTracked(name: String, properties: Properties) {
fun verifyEventTracked(name: String, properties: Properties?) {
verify { instance.capture(name, properties) }
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.test.fixtures
import im.vector.app.features.analytics.plan.UserProperties
import im.vector.app.features.analytics.plan.UserProperties.FtueUseCaseSelection
fun aUserProperties(
ftueUseCase: FtueUseCaseSelection? = FtueUseCaseSelection.Skip
) = UserProperties(
ftueUseCaseSelection = ftueUseCase
)

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.test.fixtures
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
fun aVectorAnalyticsScreen(
name: String = "a-screen-name",
properties: Map<String, Any>? = null
) = object : VectorAnalyticsScreen {
override fun getName() = name
override fun getProperties() = properties
}
fun aVectorAnalyticsEvent(
name: String = "an-event-name",
properties: Map<String, Any>? = null
) = object : VectorAnalyticsEvent {
override fun getName() = name
override fun getProperties() = properties
}