Updated on 2026-08-14
This commit is contained in:
parent
e726f26dcf
commit
06f50b0362
3 changed files with 80 additions and 56 deletions
|
|
@ -3,7 +3,6 @@ package com.tangem.features.addressbook.analytics
|
|||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_TYPE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
private const val ADDRESS_BOOK_CATEGORY = "Address Book"
|
||||
|
||||
|
|
@ -24,85 +23,85 @@ sealed class AddressBookEvents(
|
|||
data object AddressScreenOpened : AddressBookEvents(event = "Address Screen Opened")
|
||||
|
||||
class ContactListScreenOpened(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
source: Source,
|
||||
contactsCount: Int,
|
||||
) : AddressBookEvents(
|
||||
event = "Contact List Screen Opened",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
SOURCE to source.value,
|
||||
CONTACTS_COUNT to contactsCount.toString(),
|
||||
),
|
||||
) {
|
||||
enum class Source(val value: String) {
|
||||
Settings("settings"),
|
||||
SendFlow("send_flow"),
|
||||
Settings("Settings"),
|
||||
SendFlow("Send Flow"),
|
||||
}
|
||||
}
|
||||
|
||||
class AddContactTapped(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
source: Source,
|
||||
) : AddressBookEvents(
|
||||
event = "Add Contact Tapped",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
SOURCE to source.value,
|
||||
),
|
||||
) {
|
||||
enum class Source(val value: String) {
|
||||
Settings("settings"),
|
||||
SendSuccess("send_success"),
|
||||
Settings("Settings"),
|
||||
SendSuccess("Send Flow"),
|
||||
}
|
||||
}
|
||||
|
||||
class ContactSaved(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String,
|
||||
mode: Mode,
|
||||
) : AddressBookEvents(
|
||||
event = "Contact Saved",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
CONTACT_ID to contactId,
|
||||
MODE to mode.value,
|
||||
),
|
||||
) {
|
||||
enum class Mode(val value: String) {
|
||||
Create("create"),
|
||||
Edit("edit"),
|
||||
Create("Create"),
|
||||
Edit("Edit"),
|
||||
}
|
||||
}
|
||||
|
||||
class SaveErrorShown(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String?,
|
||||
errorType: ErrorType,
|
||||
) : AddressBookEvents(
|
||||
event = "Save Error Shown",
|
||||
params = buildMap {
|
||||
put(WALLET_ID, walletId.stringValue)
|
||||
put(WALLET_ID, walletId)
|
||||
contactId?.let { put(CONTACT_ID, it) }
|
||||
put(ERROR_TYPE, errorType.value)
|
||||
},
|
||||
) {
|
||||
enum class ErrorType(val value: String) {
|
||||
Network("network"),
|
||||
Server("server"),
|
||||
Signing("signing"),
|
||||
Network("Network"),
|
||||
Server("Server"),
|
||||
Signing("Signing"),
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Contact editing
|
||||
class ContactScreenOpened(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String,
|
||||
) : AddressBookEvents(
|
||||
event = "Contact Screen Opened",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
CONTACT_ID to contactId,
|
||||
),
|
||||
)
|
||||
|
|
@ -110,30 +109,30 @@ sealed class AddressBookEvents(
|
|||
|
||||
// region Send flow
|
||||
class SendFlowWidgetShown(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
) : AddressBookEvents(
|
||||
event = "Send Flow Widget Shown",
|
||||
params = mapOf(WALLET_ID to walletId.stringValue),
|
||||
params = mapOf(WALLET_ID to walletId),
|
||||
)
|
||||
|
||||
class ContactSelectedInSend(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String,
|
||||
) : AddressBookEvents(
|
||||
event = "Contact Selected",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
CONTACT_ID to contactId,
|
||||
),
|
||||
)
|
||||
|
||||
class AddressSubstitutedInSend(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String,
|
||||
) : AddressBookEvents(
|
||||
event = "Address Substituted In Send",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
CONTACT_ID to contactId,
|
||||
),
|
||||
)
|
||||
|
|
@ -141,23 +140,23 @@ sealed class AddressBookEvents(
|
|||
|
||||
// region Input errors
|
||||
class AddressInvalid(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String?,
|
||||
) : AddressBookEvents(
|
||||
event = "Address Invalid",
|
||||
params = buildMap {
|
||||
put(WALLET_ID, walletId.stringValue)
|
||||
put(WALLET_ID, walletId)
|
||||
put(CONTACT_ID, contactId.orEmpty())
|
||||
},
|
||||
)
|
||||
|
||||
class DuplicateNameErrorShown(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String?,
|
||||
) : AddressBookEvents(
|
||||
event = "Duplicate Name Error Shown",
|
||||
params = buildMap {
|
||||
put(WALLET_ID, walletId.stringValue)
|
||||
put(WALLET_ID, walletId)
|
||||
contactId?.let { put(CONTACT_ID, it) }
|
||||
},
|
||||
)
|
||||
|
|
@ -183,23 +182,23 @@ sealed class AddressBookEvents(
|
|||
|
||||
// region Deletion
|
||||
class AddressRemoved(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String,
|
||||
) : AddressBookEvents(
|
||||
event = "Address Removed",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
CONTACT_ID to contactId,
|
||||
),
|
||||
)
|
||||
|
||||
class ContactDeleted(
|
||||
walletId: UserWalletId,
|
||||
walletId: String,
|
||||
contactId: String,
|
||||
) : AddressBookEvents(
|
||||
event = "Contact Deleted",
|
||||
params = mapOf(
|
||||
WALLET_ID to walletId.stringValue,
|
||||
WALLET_ID to walletId,
|
||||
CONTACT_ID to contactId,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.addressbook.common
|
||||
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.addressbook.error.AddressBookSyncError
|
||||
import com.tangem.domain.addressbook.error.SaveContactError
|
||||
|
|
@ -60,31 +62,31 @@ internal class AddressBookAnalyticsSender @Inject constructor(
|
|||
|
||||
override fun onAddressSubstitutedInSend(walletId: UserWalletId, contactId: String) {
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.AddressSubstitutedInSend(walletId = walletId, contactId = contactId),
|
||||
AddressBookEvents.AddressSubstitutedInSend(walletId = walletId.hashForAnalytics(), contactId = contactId),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendAddressInvalid(walletId: UserWalletId, contactId: String) {
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.AddressInvalid(walletId = walletId, contactId = contactId),
|
||||
AddressBookEvents.AddressInvalid(walletId = walletId.hashForAnalytics(), contactId = contactId),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendDuplicateNameErrorShown(walletId: UserWalletId, contactId: String?) {
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.DuplicateNameErrorShown(walletId = walletId, contactId = contactId),
|
||||
AddressBookEvents.DuplicateNameErrorShown(walletId = walletId.hashForAnalytics(), contactId = contactId),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendAddressRemoved(walletId: UserWalletId, contactId: String) {
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.AddressRemoved(walletId = walletId, contactId = contactId),
|
||||
AddressBookEvents.AddressRemoved(walletId = walletId.hashForAnalytics(), contactId = contactId),
|
||||
)
|
||||
}
|
||||
|
||||
fun sendContactDeleted(walletId: UserWalletId, contactId: String) {
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.ContactDeleted(walletId = walletId, contactId = contactId),
|
||||
AddressBookEvents.ContactDeleted(walletId = walletId.hashForAnalytics(), contactId = contactId),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +116,7 @@ internal class AddressBookAnalyticsSender @Inject constructor(
|
|||
fun sendContactSaved(walletId: UserWalletId, contactId: String, isEdit: Boolean) {
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.ContactSaved(
|
||||
walletId = walletId,
|
||||
walletId = walletId.hashForAnalytics(),
|
||||
contactId = contactId,
|
||||
mode = if (isEdit) {
|
||||
AddressBookEvents.ContactSaved.Mode.Edit
|
||||
|
|
@ -133,7 +135,7 @@ internal class AddressBookAnalyticsSender @Inject constructor(
|
|||
val errorType = error.toErrorType() ?: return
|
||||
analyticsEventHandler.send(
|
||||
AddressBookEvents.SaveErrorShown(
|
||||
walletId = walletId,
|
||||
walletId = walletId.hashForAnalytics(),
|
||||
contactId = contactId,
|
||||
errorType = errorType,
|
||||
),
|
||||
|
|
@ -158,8 +160,12 @@ internal class AddressBookAnalyticsSender @Inject constructor(
|
|||
analyticsEventHandler.send(AddressBookEvents.AddressScreenOpened)
|
||||
}
|
||||
|
||||
private suspend fun selectedWalletId(): UserWalletId = userWalletsListRepository.selectedUserWallet
|
||||
private suspend fun selectedWalletId(): String = userWalletsListRepository.selectedUserWallet
|
||||
.filterNotNull()
|
||||
.first()
|
||||
.walletId
|
||||
.hashForAnalytics()
|
||||
|
||||
/** Analytics must never receive a raw wallet id — send its SHA-256 (uppercase hex) instead. */
|
||||
private fun UserWalletId.hashForAnalytics(): String = value.calculateSha256().toHexString()
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.addressbook.common
|
||||
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.addressbook.error.AddressBookSyncError
|
||||
import com.tangem.domain.addressbook.error.SaveContactError
|
||||
|
|
@ -56,7 +58,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.ContactListScreenOpened(
|
||||
walletId = EXPECTED_WALLET_ID,
|
||||
walletId = EXPECTED_WALLET_ID_HASH,
|
||||
source = model.source,
|
||||
contactsCount = model.contactsCount,
|
||||
)
|
||||
|
|
@ -72,7 +74,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
|
||||
// Assert
|
||||
val expected = AddContactTapped(
|
||||
walletId = EXPECTED_WALLET_ID,
|
||||
walletId = EXPECTED_WALLET_ID_HASH,
|
||||
source = model.expectedSource,
|
||||
)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
|
|
@ -86,7 +88,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
|
||||
// Assert
|
||||
val expected = ContactSaved(
|
||||
walletId = EXPECTED_WALLET_ID,
|
||||
walletId = EXPECTED_WALLET_ID_HASH,
|
||||
contactId = CONTACT_ID,
|
||||
mode = model.expectedMode,
|
||||
)
|
||||
|
|
@ -105,7 +107,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
verify(exactly = 0) { analyticsEventHandler.send(any()) }
|
||||
} else {
|
||||
val expected = AddressBookEvents.SaveErrorShown(
|
||||
walletId = EXPECTED_WALLET_ID,
|
||||
walletId = EXPECTED_WALLET_ID_HASH,
|
||||
contactId = CONTACT_ID,
|
||||
errorType = expectedType,
|
||||
)
|
||||
|
|
@ -138,7 +140,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.ContactScreenOpened(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
val expected = AddressBookEvents.ContactScreenOpened(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +151,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.SendFlowWidgetShown(walletId = EXPECTED_WALLET_ID)
|
||||
val expected = AddressBookEvents.SendFlowWidgetShown(walletId = EXPECTED_WALLET_ID_HASH)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +162,10 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
advanceUntilIdle()
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.ContactSelectedInSend(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
val expected = AddressBookEvents.ContactSelectedInSend(
|
||||
walletId = EXPECTED_WALLET_ID_HASH,
|
||||
contactId = CONTACT_ID,
|
||||
)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +175,8 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
sender.onAddressSubstitutedInSend(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.AddressSubstitutedInSend(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
val expected =
|
||||
AddressBookEvents.AddressSubstitutedInSend(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +187,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
sender.sendAddressInvalid(walletId = EXPECTED_WALLET_ID, contactId = contactId)
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.AddressInvalid(walletId = EXPECTED_WALLET_ID, contactId = contactId)
|
||||
val expected = AddressBookEvents.AddressInvalid(walletId = EXPECTED_WALLET_ID_HASH, contactId = contactId)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +198,10 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
sender.sendDuplicateNameErrorShown(walletId = EXPECTED_WALLET_ID, contactId = contactId)
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.DuplicateNameErrorShown(walletId = EXPECTED_WALLET_ID, contactId = contactId)
|
||||
val expected = AddressBookEvents.DuplicateNameErrorShown(
|
||||
walletId = EXPECTED_WALLET_ID_HASH,
|
||||
contactId = contactId,
|
||||
)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +211,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
sender.sendAddressRemoved(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.AddressRemoved(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
val expected = AddressBookEvents.AddressRemoved(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +221,7 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
sender.sendContactDeleted(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
|
||||
// Assert
|
||||
val expected = AddressBookEvents.ContactDeleted(walletId = EXPECTED_WALLET_ID, contactId = CONTACT_ID)
|
||||
val expected = AddressBookEvents.ContactDeleted(walletId = EXPECTED_WALLET_ID_HASH, contactId = CONTACT_ID)
|
||||
verify(exactly = 1) { analyticsEventHandler.send(expected) }
|
||||
}
|
||||
|
||||
|
|
@ -241,9 +250,15 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
|
||||
private fun provideSaveErrorModels() = listOf(
|
||||
SaveErrorModel(error = SaveContactError.Signing(mockk()), expectedType = ErrorType.Signing),
|
||||
SaveErrorModel(error = SaveContactError.Backend(AddressBookSyncError.Network), expectedType = ErrorType.Network),
|
||||
SaveErrorModel(
|
||||
error = SaveContactError.Backend(AddressBookSyncError.Network),
|
||||
expectedType = ErrorType.Network,
|
||||
),
|
||||
// 412
|
||||
SaveErrorModel(error = SaveContactError.Backend(AddressBookSyncError.Conflict), expectedType = ErrorType.Server),
|
||||
SaveErrorModel(
|
||||
error = SaveContactError.Backend(AddressBookSyncError.Conflict),
|
||||
expectedType = ErrorType.Server,
|
||||
),
|
||||
// 5xx / unmapped
|
||||
SaveErrorModel(error = SaveContactError.Backend(AddressBookSyncError.Unknown), expectedType = ErrorType.Server),
|
||||
SaveErrorModel(
|
||||
|
|
@ -262,6 +277,10 @@ internal class AddressBookAnalyticsSenderTest {
|
|||
|
||||
private companion object {
|
||||
val EXPECTED_WALLET_ID = UserWalletId("0011223344")
|
||||
|
||||
// Analytics receives the SHA-256 (uppercase hex) of the wallet id, never the raw value.
|
||||
val EXPECTED_WALLET_ID_HASH = EXPECTED_WALLET_ID.value.calculateSha256().toHexString()
|
||||
|
||||
const val CONTACT_ID = "contact-42"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue