diff --git a/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/MemberIdService.kt b/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/MemberIdService.kt index c5adaeb592..cf017ffb6d 100644 --- a/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/MemberIdService.kt +++ b/app/auth/auth-core-public/src/main/kotlin/com/hedvig/android/auth/MemberIdService.kt @@ -3,6 +3,7 @@ package com.hedvig.android.auth import android.util.Base64 import com.hedvig.android.auth.storage.AuthTokenStorage import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json @@ -19,7 +20,7 @@ class MemberIdService( authTokens?.accessToken?.token?.let { stringToken -> extractMemberIdFromAccessToken(stringToken) } - } + }.distinctUntilChanged() } /** diff --git a/app/core/core-datastore-public/src/main/kotlin/com/hedvig/android/core/datastore/DeviceIdDataStore.kt b/app/core/core-datastore-public/src/main/kotlin/com/hedvig/android/core/datastore/DeviceIdDataStore.kt index 836580afdf..ae3cab2941 100644 --- a/app/core/core-datastore-public/src/main/kotlin/com/hedvig/android/core/datastore/DeviceIdDataStore.kt +++ b/app/core/core-datastore-public/src/main/kotlin/com/hedvig/android/core/datastore/DeviceIdDataStore.kt @@ -8,6 +8,7 @@ import java.util.UUID import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -31,8 +32,8 @@ internal class DeviceIdDataStoreImpl( } } - override fun observeDeviceId(): Flow { - return dataStore.data.map { it[key] ?: "" } + override fun observeDeviceId(): Flow { // todo return null instead of "" for uninitialized cases? + return dataStore.data.map { it[key] ?: "" }.distinctUntilChanged() } private suspend fun generateDeviceId() { diff --git a/app/core/core-demo-mode/src/main/kotlin/com/hedvig/android/core/demomode/DemoManager.kt b/app/core/core-demo-mode/src/main/kotlin/com/hedvig/android/core/demomode/DemoManager.kt index db1e5b9cb9..ffd6b79dec 100644 --- a/app/core/core-demo-mode/src/main/kotlin/com/hedvig/android/core/demomode/DemoManager.kt +++ b/app/core/core-demo-mode/src/main/kotlin/com/hedvig/android/core/demomode/DemoManager.kt @@ -5,6 +5,7 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map interface DemoManager { @@ -19,7 +20,7 @@ internal class DataStoreDemoManager( override fun isDemoMode(): Flow { return dataStore.data.map { it[demoModeKey] ?: false - } + }.distinctUntilChanged() } override suspend fun setDemoMode(demoMode: Boolean) { diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/DatadogInitializer.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/DatadogInitializer.kt index 271e3f046a..10b78c39a4 100644 --- a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/DatadogInitializer.kt +++ b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/DatadogInitializer.kt @@ -17,14 +17,9 @@ import com.datadog.android.trace.AndroidTracer import com.datadog.android.trace.Trace import com.datadog.android.trace.TraceConfiguration import com.hedvig.android.core.buildconstants.HedvigBuildConstants -import com.hedvig.android.core.common.ApplicationScope -import com.hedvig.android.core.datastore.DeviceIdDataStore -import com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager import com.hedvig.android.logger.LogPriority import com.hedvig.android.logger.logcat import io.opentracing.util.GlobalTracer -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject import timber.log.Timber @@ -32,9 +27,6 @@ import timber.log.Timber // Used in /app/src/main/AndroidManifest.xml abstract class DatadogInitializer : Initializer, KoinComponent { private val hedvigBuildConstants by inject() - private val deviceIdDataStore by inject() - private val applicationScope by inject() - private val datadogAttributesManager by inject() override fun create(context: Context) { val clientToken = "pub185bcba7ed324e83d068b80e25a81359" @@ -96,13 +88,5 @@ abstract class DatadogInitializer : Initializer, KoinComponent { .build() Timber.plant(DatadogLoggingTree(logger)) - - applicationScope.launch { - val deviceId = deviceIdDataStore.observeDeviceId().first() - datadogAttributesManager.storeAttribute(DEVICE_ID_KEY, deviceId) - logcat(LogPriority.VERBOSE) { "Datadog stored device id attribute: $deviceId" } - } } } - -private const val DEVICE_ID_KEY = "device_id" diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DatadogAttributesManager.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DatadogAttributesManager.kt index bff32e15ed..51c52133c2 100644 --- a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DatadogAttributesManager.kt +++ b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DatadogAttributesManager.kt @@ -2,23 +2,52 @@ package com.hedvig.android.datadog.core.attributestracking import com.datadog.android.Datadog import com.datadog.android.rum.GlobalRumMonitor +import com.hedvig.android.core.common.ApplicationScope +import com.hedvig.android.initializable.Initializable +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch -interface DatadogAttributesManager { - fun storeAttribute(key: String, value: Any?) - - fun deleteAttribute(key: String) +interface DatadogMemberIdProvider { + /** + * The implementation must return something, even null, immediatelly. To avoid blocking the rest of the attributes + * from being read in the [combine] which combines all of them together + */ + fun provide(): Flow> } -internal class DatadogAttributesManagerImpl : DatadogAttributesManager { - override fun storeAttribute(key: String, value: Any?) { - val sdkCore = Datadog.getInstance() - sdkCore.addUserProperties(mapOf(key to value)) - GlobalRumMonitor.get(sdkCore).addAttribute(key, value) - } +interface DatadogAttributeProvider { + /** + * The implementation must return something, even null, immediatelly. To avoid blocking the rest of the attributes + * from being read in the [combine] which combines all of them together + */ + fun provide(): Flow> +} - override fun deleteAttribute(key: String) { +internal class DatadogAttributesManager( + private val applicationScope: ApplicationScope, + private val memberIdProvider: DatadogMemberIdProvider, + private val attributeProviders: Set, +) : Initializable { + override fun initialize() { val sdkCore = Datadog.getInstance() - sdkCore.addUserProperties(mapOf(key to null)) - GlobalRumMonitor.get(sdkCore).removeAttribute(key) + val rumMonitor = GlobalRumMonitor.get(sdkCore) + applicationScope.launch { + combine( + memberIdProvider.provide(), + combine(attributeProviders.map { it.provide() }) { it.toMap() }, + ) { memberId: Pair, attributes: Map -> + memberId to attributes + }.collect { (memberIdAttribute, attributes): Pair, Map> -> + logcat(LogPriority.VERBOSE) { "DatadogAttributesManager storing: $memberIdAttribute, $attributes" } + sdkCore.setUserInfo(id = memberIdAttribute.second, name = null, email = null, extraInfo = attributes) + rumMonitor.addAttribute(memberIdAttribute.first, memberIdAttribute.second) + for (attribute in attributes) { + rumMonitor.addAttribute(attribute.key, attribute.value) + } + } + } } } diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DeviceIdProvider.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DeviceIdProvider.kt new file mode 100644 index 0000000000..89a71e77e1 --- /dev/null +++ b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/DeviceIdProvider.kt @@ -0,0 +1,27 @@ +package com.hedvig.android.datadog.core.attributestracking + +import com.hedvig.android.core.datastore.DeviceIdDataStore +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +internal class DeviceIdProvider( + private val deviceIdDataStore: DeviceIdDataStore, +) : DatadogAttributeProvider { + override fun provide(): Flow> { + return deviceIdDataStore + .observeDeviceId() + .onEach { deviceId -> + logcat(LogPriority.VERBOSE) { "Datadog stored device id attribute: $deviceId" } + } + .map { deviceId -> + DEVICE_ID_KEY to deviceId + } + } + + companion object { + private const val DEVICE_ID_KEY = "device_id" + } +} diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/MemberMemberIdTrackingKey.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/MemberMemberIdTrackingKey.kt new file mode 100644 index 0000000000..6f12af369d --- /dev/null +++ b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/attributestracking/MemberMemberIdTrackingKey.kt @@ -0,0 +1,35 @@ +package com.hedvig.android.datadog.core.attributestracking + +import com.hedvig.android.auth.MemberIdService +import com.hedvig.android.logger.LogPriority +import com.hedvig.android.logger.logcat +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +internal class DatadogMemberIdProviderImpl( + private val memberIdService: MemberIdService, +) : DatadogMemberIdProvider { + override fun provide(): Flow> { + return memberIdService + .getMemberId() + .map { MEMBER_ID_TRACKING_KEY to it } + .onEach { (key, memberId) -> + logcat(LogPriority.INFO) { + if (memberId == null) { + "Removing from global RUM attribute:$MEMBER_ID_TRACKING_KEY" + } else { + "Appending to global RUM attribute:$MEMBER_ID_TRACKING_KEY = $memberId" + } + } + } + } + + companion object { + /** + * Key to be passed to [com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager] to track the + * member ID after we've logged into the app + */ + private const val MEMBER_ID_TRACKING_KEY = "member_id" + } +} diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/di/DatadogModule.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/di/DatadogModule.kt index 5905cbb967..319cfb277a 100644 --- a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/di/DatadogModule.kt +++ b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/di/DatadogModule.kt @@ -2,9 +2,11 @@ package com.hedvig.android.datadog.core.di import com.hedvig.android.auth.MemberIdService import com.hedvig.android.core.common.ApplicationScope +import com.hedvig.android.datadog.core.attributestracking.DatadogAttributeProvider import com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager -import com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManagerImpl -import com.hedvig.android.datadog.core.memberid.DatadogMemberIdUpdater +import com.hedvig.android.datadog.core.attributestracking.DatadogMemberIdProvider +import com.hedvig.android.datadog.core.attributestracking.DatadogMemberIdProviderImpl +import com.hedvig.android.datadog.core.attributestracking.DeviceIdProvider import com.hedvig.android.initializable.Initializable import io.opentracing.Tracer import io.opentracing.util.GlobalTracer @@ -12,13 +14,20 @@ import org.koin.dsl.bind import org.koin.dsl.module val datadogModule = module { - single { - DatadogMemberIdUpdater( - get(), - get(), + single { GlobalTracer.get() } + + single { + DatadogMemberIdProviderImpl(get()) + } + single { + DeviceIdProvider(get()) + } bind DatadogAttributeProvider::class + + single { + DatadogAttributesManager( get(), + get(), + getAll().toSet(), ) } bind Initializable::class - single { GlobalTracer.get() } - single { DatadogAttributesManagerImpl() } } diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/memberid/DatadogMemberIdUpdater.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/memberid/DatadogMemberIdUpdater.kt deleted file mode 100644 index 4963d068c5..0000000000 --- a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/memberid/DatadogMemberIdUpdater.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.hedvig.android.datadog.core.memberid - -import com.hedvig.android.auth.MemberIdService -import com.hedvig.android.core.common.ApplicationScope -import com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager -import com.hedvig.android.initializable.Initializable -import com.hedvig.android.logger.LogPriority -import com.hedvig.android.logger.logcat -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch - -internal class DatadogMemberIdUpdater( - private val datadogAttributesManager: DatadogAttributesManager, - private val memberIdService: MemberIdService, - private val applicationScope: ApplicationScope, -) : Initializable { - override fun initialize() { - applicationScope.launch { - memberIdService.getMemberId().collectLatest { memberId -> - if (memberId == null) { - logcat(LogPriority.INFO) { "Removing from global RUM attribute:$MEMBER_ID_TRACKING_KEY" } - datadogAttributesManager.deleteAttribute(MEMBER_ID_TRACKING_KEY) - } else { - logcat(LogPriority.INFO) { "Appending to global RUM attribute:$MEMBER_ID_TRACKING_KEY = $memberId" } - datadogAttributesManager.storeAttribute(MEMBER_ID_TRACKING_KEY, memberId) - } - } - } - } -} diff --git a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/memberid/MemberIdTrackingKey.kt b/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/memberid/MemberIdTrackingKey.kt deleted file mode 100644 index 193f186e56..0000000000 --- a/app/datadog/datadog-core/src/main/kotlin/com/hedvig/android/datadog/core/memberid/MemberIdTrackingKey.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.hedvig.android.datadog.core.memberid - -/** - * Key to be passed to [com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager] to track the - * member ID after we've logged into the app - */ -internal const val MEMBER_ID_TRACKING_KEY = "member_id" diff --git a/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/DatadogDemoModeTracking.kt b/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/DatadogDemoModeTracking.kt index 87405534a3..66a0af09bf 100644 --- a/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/DatadogDemoModeTracking.kt +++ b/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/DatadogDemoModeTracking.kt @@ -1,26 +1,23 @@ package com.hedvig.android.datadog.demo.tracking -import com.hedvig.android.core.common.ApplicationScope import com.hedvig.android.core.demomode.DemoManager -import com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager -import com.hedvig.android.initializable.Initializable +import com.hedvig.android.datadog.core.attributestracking.DatadogAttributeProvider import com.hedvig.android.logger.LogPriority import com.hedvig.android.logger.logcat -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map class DatadogDemoModeTracking( - private val applicationScope: ApplicationScope, private val demoManager: DemoManager, - private val datadogAttributesManager: DatadogAttributesManager, -) : Initializable { - override fun initialize() { - applicationScope.launch { - demoManager.isDemoMode().collect { isDemoMode -> - logcat(LogPriority.INFO) { "Demo mode changed to:$isDemoMode" } - datadogAttributesManager.storeAttribute(IS_DEMO_MODE_TRACKING_KEY, isDemoMode) - } +) : DatadogAttributeProvider { + override fun provide(): Flow> { + return demoManager.isDemoMode().map { isDemoMode -> + logcat(LogPriority.INFO) { "Demo mode changed to:$isDemoMode" } + IS_DEMO_MODE_TRACKING_KEY to isDemoMode } } -} -private const val IS_DEMO_MODE_TRACKING_KEY = "is_demo_mode" + companion object { + private const val IS_DEMO_MODE_TRACKING_KEY = "is_demo_mode" + } +} diff --git a/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/di/DatadogDemoTrackingModule.kt b/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/di/DatadogDemoTrackingModule.kt index 77b4c0d517..994775aff5 100644 --- a/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/di/DatadogDemoTrackingModule.kt +++ b/app/datadog/datadog-demo-tracking/src/main/kotlin/com/hedvig/android/datadog/demo/tracking/di/DatadogDemoTrackingModule.kt @@ -1,15 +1,13 @@ package com.hedvig.android.datadog.demo.tracking.di -import com.hedvig.android.core.common.ApplicationScope import com.hedvig.android.core.demomode.DemoManager -import com.hedvig.android.datadog.core.attributestracking.DatadogAttributesManager +import com.hedvig.android.datadog.core.attributestracking.DatadogAttributeProvider import com.hedvig.android.datadog.demo.tracking.DatadogDemoModeTracking -import com.hedvig.android.initializable.Initializable import org.koin.dsl.bind import org.koin.dsl.module val datadogDemoTrackingModule = module { single { - DatadogDemoModeTracking(get(), get(), get()) - } bind Initializable::class + DatadogDemoModeTracking(get()) + } bind DatadogAttributeProvider::class }