Merge pull request #5239 from vector-im/feature/adm/missing-pre-consent-identity-values

Fixing missing identify properties
This commit is contained in:
Benoit Marty 2022-02-17 16:35:15 +01:00 committed by GitHub
commit 6784caab9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 753 additions and 55 deletions

1
changelog.d/5234.bugfix Normal file
View File

@ -0,0 +1 @@
Analytics: Fixes missing use case identity values from within the onboarding flow

View File

@ -0,0 +1,23 @@
/*
* 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.core.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class NamedGlobalScope

View File

@ -29,11 +29,13 @@ import dagger.hilt.components.SingletonComponent
import im.vector.app.BuildConfig
import im.vector.app.EmojiCompatWrapper
import im.vector.app.EmojiSpanify
import im.vector.app.config.analyticsConfig
import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.time.Clock
import im.vector.app.core.time.DefaultClock
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
@ -48,6 +50,7 @@ import im.vector.app.features.ui.SharedPreferencesUiStateRepository
import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
@ -159,4 +162,16 @@ object VectorStaticModule {
fun providesCoroutineDispatchers(): CoroutineDispatchers {
return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default)
}
@Suppress("EXPERIMENTAL_API_USAGE")
@Provides
@NamedGlobalScope
fun providesGlobalScope(): CoroutineScope {
return GlobalScope
}
@Provides
fun providesAnalyticsConfig(): AnalyticsConfig {
return analyticsConfig
}
}

View File

@ -16,19 +16,18 @@
package im.vector.app.features.analytics.impl
import android.content.Context
import com.posthog.android.Options
import com.posthog.android.PostHog
import com.posthog.android.Properties
import im.vector.app.BuildConfig
import im.vector.app.config.analyticsConfig
import im.vector.app.core.di.NamedGlobalScope
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.log.analyticsTag
import im.vector.app.features.analytics.plan.UserProperties
import im.vector.app.features.analytics.store.AnalyticsStore
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -41,15 +40,30 @@ private val IGNORED_OPTIONS: Options? = null
@Singleton
class DefaultVectorAnalytics @Inject constructor(
private val context: Context,
private val analyticsStore: AnalyticsStore
postHogFactory: PostHogFactory,
analyticsConfig: AnalyticsConfig,
private val analyticsStore: AnalyticsStore,
private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory,
@NamedGlobalScope private val globalScope: CoroutineScope
) : VectorAnalytics {
private var posthog: PostHog? = null
private val posthog: PostHog? = when {
analyticsConfig.isEnabled -> postHogFactory.createPosthog()
else -> {
Timber.tag(analyticsTag.value).w("Analytics is disabled")
null
}
}
// Cache for the store values
private var userConsent: Boolean? = null
private var analyticsId: String? = null
override fun init() {
observeUserConsent()
observeAnalyticsId()
}
override fun getUserConsent(): Flow<Boolean> {
return analyticsStore.userConsentFlow
}
@ -82,13 +96,6 @@ class DefaultVectorAnalytics @Inject constructor(
setAnalyticsId("")
}
override fun init() {
observeUserConsent()
observeAnalyticsId()
createAnalyticsClient()
}
@Suppress("EXPERIMENTAL_API_USAGE")
private fun observeAnalyticsId() {
getAnalyticsId()
.onEach { id ->
@ -96,21 +103,20 @@ class DefaultVectorAnalytics @Inject constructor(
analyticsId = id
identifyPostHog()
}
.launchIn(GlobalScope)
.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)
}
}
@Suppress("EXPERIMENTAL_API_USAGE")
private fun observeUserConsent() {
getUserConsent()
.onEach { consent ->
@ -118,49 +124,13 @@ class DefaultVectorAnalytics @Inject constructor(
userConsent = consent
optOutPostHog()
}
.launchIn(GlobalScope)
.launchIn(globalScope)
}
private fun optOutPostHog() {
userConsent?.let { posthog?.optOut(!it) }
}
private fun createAnalyticsClient() {
Timber.tag(analyticsTag.value).d("createAnalyticsClient()")
if (analyticsConfig.isEnabled.not()) {
Timber.tag(analyticsTag.value).w("Analytics is disabled")
return
}
posthog = 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()
optOutPostHog()
identifyPostHog()
}
private fun getLogLevel(): PostHog.LogLevel {
return if (BuildConfig.DEBUG) {
PostHog.LogLevel.DEBUG
} else {
PostHog.LogLevel.INFO
}
}
override fun capture(event: VectorAnalyticsEvent) {
Timber.tag(analyticsTag.value).d("capture($event)")
posthog

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.features.analytics.impl
import android.content.Context
import im.vector.app.ActiveSessionDataSource
import im.vector.app.core.extensions.vectorStore
import im.vector.app.features.analytics.extensions.toTrackingValue
import im.vector.app.features.analytics.plan.UserProperties
import javax.inject.Inject
class LateInitUserPropertiesFactory @Inject constructor(
private val activeSessionDataSource: ActiveSessionDataSource,
private val context: Context,
) {
suspend fun createUserProperties(): UserProperties? {
val useCase = activeSessionDataSource.currentValue?.orNull()?.vectorStore(context)?.readUseCase()
return useCase?.let {
UserProperties(ftueUseCaseSelection = it.toTrackingValue())
}
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.features.analytics.impl
import android.content.Context
import com.posthog.android.PostHog
import im.vector.app.BuildConfig
import im.vector.app.config.analyticsConfig
import javax.inject.Inject
class PostHogFactory @Inject constructor(private val context: Context) {
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 (BuildConfig.DEBUG) {
PostHog.LogLevel.DEBUG
} else {
PostHog.LogLevel.INFO
}
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.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
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
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
private const val AN_ANALYTICS_ID = "analytics-id"
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),
lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance
)
@Before
fun setUp() {
defaultVectorAnalytics.init()
}
@Test
fun `when setting user consent then updates analytics store`() = runBlockingTest {
defaultVectorAnalytics.setUserConsent(true)
fakeAnalyticsStore.verifyConsentUpdated(updatedValue = true)
}
@Test
fun `when consenting to analytics then updates posthog opt out to false`() = runBlockingTest {
fakeAnalyticsStore.givenUserContent(consent = true)
fakePostHog.verifyOptOutStatus(optedOut = false)
}
@Test
fun `when revoking consent to analytics then updates posthog opt out to true`() = runBlockingTest {
fakeAnalyticsStore.givenUserContent(consent = false)
fakePostHog.verifyOptOutStatus(optedOut = true)
}
@Test
fun `when setting the analytics id then updates analytics store`() = runBlockingTest {
defaultVectorAnalytics.setAnalyticsId(AN_ANALYTICS_ID)
fakeAnalyticsStore.verifyAnalyticsIdUpdated(updatedValue = AN_ANALYTICS_ID)
}
@Test
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, A_LATE_INIT_USER_PROPERTIES)
}
@Test
fun `when signing out then resets posthog`() = runBlockingTest {
fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()
defaultVectorAnalytics.onSignOut()
fakePostHog.verifyReset()
}
@Test
fun `given user consent when tracking screen events then submits to posthog`() = runBlockingTest {
fakeAnalyticsStore.givenUserContent(consent = true)
defaultVectorAnalytics.screen(A_SCREEN_EVENT)
fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), A_SCREEN_EVENT.toPostHogProperties())
}
@Test
fun `given user has not consented when tracking screen events then does not track`() = runBlockingTest {
fakeAnalyticsStore.givenUserContent(consent = false)
defaultVectorAnalytics.screen(A_SCREEN_EVENT)
fakePostHog.verifyNoScreenTracking()
}
@Test
fun `given user consent when tracking events then submits to posthog`() = runBlockingTest {
fakeAnalyticsStore.givenUserContent(consent = true)
defaultVectorAnalytics.capture(AN_EVENT)
fakePostHog.verifyEventTracked(AN_EVENT.getName(), AN_EVENT.toPostHogProperties())
}
@Test
fun `given user has not consented when tracking events then does not track`() = runBlockingTest {
fakeAnalyticsStore.givenUserContent(consent = false)
defaultVectorAnalytics.capture(AN_EVENT)
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,73 @@
/*
* 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.features.analytics.impl
import im.vector.app.features.analytics.plan.UserProperties
import im.vector.app.features.onboarding.FtueUseCase
import im.vector.app.test.fakes.FakeActiveSessionDataSource
import im.vector.app.test.fakes.FakeContext
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.fakes.FakeVectorStore
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
@ExperimentalCoroutinesApi
class LateInitUserPropertiesFactoryTest {
private val fakeActiveSessionDataSource = FakeActiveSessionDataSource()
private val fakeVectorStore = FakeVectorStore()
private val fakeContext = FakeContext()
private val fakeSession = FakeSession().also {
it.givenVectorStore(fakeVectorStore.instance)
}
private val lateInitUserProperties = LateInitUserPropertiesFactory(
fakeActiveSessionDataSource.instance,
fakeContext.instance
)
@Test
fun `given no active session when creating properties then returns null`() = runBlockingTest {
val result = lateInitUserProperties.createUserProperties()
result shouldBeEqualTo null
}
@Test
fun `given no use case set on an active session when creating properties then returns null`() = runBlockingTest {
fakeVectorStore.givenUseCase(null)
fakeSession.givenVectorStore(fakeVectorStore.instance)
fakeActiveSessionDataSource.setActiveSession(fakeSession)
val result = lateInitUserProperties.createUserProperties()
result shouldBeEqualTo null
}
@Test
fun `given use case set on an active session when creating properties then includes the use case`() = runBlockingTest {
fakeVectorStore.givenUseCase(FtueUseCase.TEAMS)
fakeActiveSessionDataSource.setActiveSession(fakeSession)
val result = lateInitUserProperties.createUserProperties()
result shouldBeEqualTo UserProperties(
ftueUseCaseSelection = UserProperties.FtueUseCaseSelection.WorkMessaging
)
}
}

View File

@ -0,0 +1,30 @@
/*
* 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 arrow.core.Option
import im.vector.app.ActiveSessionDataSource
import org.matrix.android.sdk.api.session.Session
class FakeActiveSessionDataSource {
val instance = ActiveSessionDataSource()
fun setActiveSession(session: Session) {
instance.post(Option.just(session))
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.store.AnalyticsStore
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.runBlocking
class FakeAnalyticsStore {
private val _consentFlow = MutableSharedFlow<Boolean>()
private val _idFlow = MutableSharedFlow<String>()
val instance = mockk<AnalyticsStore>(relaxed = true) {
every { userConsentFlow } returns _consentFlow
every { analyticsIdFlow } returns _idFlow
}
fun allowSettingAnalyticsIdToCallBackingFlow() {
coEvery { instance.setAnalyticsId(any()) } answers {
runBlocking { _idFlow.emit(firstArg()) }
}
}
fun verifyConsentUpdated(updatedValue: Boolean) {
coVerify { instance.setUserConsent(updatedValue) }
}
suspend fun givenUserContent(consent: Boolean) {
_consentFlow.emit(consent)
}
fun verifyAnalyticsIdUpdated(updatedValue: String) {
coVerify { instance.setAnalyticsId(updatedValue) }
}
suspend fun givenAnalyticsId(id: String) {
_idFlow.emit(id)
}
}

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

@ -0,0 +1,81 @@
/*
* 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 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
import io.mockk.verify
class FakePostHog {
init {
// workaround to avoid PostHog.HANDLER failing
mockkStatic(Looper::class)
val looper = mockk<Looper> {
every { thread } returns Thread.currentThread()
}
every { Looper.getMainLooper() } returns looper
}
val instance = mockk<PostHog>(relaxed = true)
fun verifyOptOutStatus(optedOut: Boolean) {
verify { instance.optOut(optedOut) }
}
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?) {
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 verifyNoEventTracking() {
verify(exactly = 0) {
instance.capture(any())
instance.capture(any(), any())
instance.capture(any(), any(), any())
}
}
}

View File

@ -0,0 +1,28 @@
/*
* 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 com.posthog.android.PostHog
import im.vector.app.features.analytics.impl.PostHogFactory
import io.mockk.every
import io.mockk.mockk
class FakePostHogFactory(postHog: PostHog) {
val instance = mockk<PostHogFactory>().also {
every { it.createPosthog() } returns postHog
}
}

View File

@ -16,8 +16,12 @@
package im.vector.app.test.fakes
import im.vector.app.core.extensions.vectorStore
import im.vector.app.features.session.VectorSessionStore
import im.vector.app.test.testCoroutineDispatchers
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.mockkStatic
import org.matrix.android.sdk.api.session.Session
class FakeSession(
@ -25,7 +29,19 @@ class FakeSession(
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService()
) : Session by mockk(relaxed = true) {
init {
mockkStatic("im.vector.app.core.extensions.SessionKt")
}
override fun cryptoService() = fakeCryptoService
override val sharedSecretStorageService = fakeSharedSecretStorageService
override val coroutineDispatchers = testCoroutineDispatchers
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
coEvery {
this@FakeSession.vectorStore(any())
} coAnswers {
vectorSessionStore
}
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.onboarding.FtueUseCase
import im.vector.app.features.session.VectorSessionStore
import io.mockk.coEvery
import io.mockk.mockk
class FakeVectorStore {
val instance = mockk<VectorSessionStore>()
fun givenUseCase(useCase: FtueUseCase?) {
coEvery {
instance.readUseCase()
} coAnswers {
useCase
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.AnalyticsConfig
object AnalyticsConfigFixture {
fun anAnalyticsConfig(
isEnabled: Boolean = false,
postHogHost: String = "http://posthog.url",
postHogApiKey: String = "api-key",
policyLink: String = "http://policy.link"
) = object : AnalyticsConfig {
override val isEnabled: Boolean = isEnabled
override val postHogHost = postHogHost
override val postHogApiKey = postHogApiKey
override val policyLink = policyLink
}
}

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
}