Skip to content
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

feat: add analytics user profile - Part 2 (WPB-8978) #2877

Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7ba6c47
feat: add method to set/insert tracking identifier into user metadata…
alexandreferris Jul 8, 2024
27c8163
feat: add MessageContent for DataTransfer with TrackingIdentifier
alexandreferris Jul 8, 2024
27b9220
feat: add DataTransferEventHandler to handle new data transfer events…
alexandreferris Jul 8, 2024
c2a1105
chore: remove needless blank line
alexandreferris Jul 8, 2024
e05e102
feat: add get for current tracking identifier and get and set for pre…
alexandreferris Jul 12, 2024
46d744f
feat: add usage and logic handling for current and previous tracking …
alexandreferris Jul 12, 2024
43db0b6
feat: add tests
alexandreferris Jul 12, 2024
cf4cb58
Merge branch 'feat/add_analytics_user_profile_epic' into feat/add_ana…
alexandreferris Jul 12, 2024
89e8824
feat: add better handling of receiving already existing tracking id a…
alexandreferris Jul 12, 2024
91772f5
feat: add new analytics logger to kalium logger
alexandreferris Jul 12, 2024
6bed9be
feat: add usecase to observe analytics tracking identifier
alexandreferris Jul 15, 2024
1748248
feat: add tests for observeAnalyticsTrackingIdentifierStatus
alexandreferris Jul 15, 2024
ec1d55d
feat: add none AnalyticsIdentifierResult to be used in AR and remove …
alexandreferris Jul 15, 2024
9887094
Merge branch 'develop' into feat/add_analytics_user_profile_part2
alexandreferris Jul 15, 2024
efb3ed5
feat: add proper mapping to observer result and verification on Eithe…
alexandreferris Jul 15, 2024
bdd660c
feat: add tests for observer with new logic
alexandreferris Jul 15, 2024
217d216
chore: add user scope logger
alexandreferris Jul 15, 2024
cfbc9b2
chore: add `as` instantiation of right value for usecase result
alexandreferris Jul 17, 2024
1e16b82
Merge branch 'develop' into feat/add_analytics_user_profile_part2
alexandreferris Jul 17, 2024
41066e2
feat: move AnalyticsIdentifierResult to new data module and update it…
alexandreferris Jul 17, 2024
495e142
feat: add usecase to delete previous tracking identifier
alexandreferris Jul 18, 2024
1273307
Merge branch 'develop' into feat/add_analytics_user_profile_part2
alexandreferris Jul 18, 2024
5f03b4d
Merge branch 'feat/add_analytics_user_profile_epic' into feat/add_ana…
alexandreferris Jul 18, 2024
57b3670
chore: add missing imports
alexandreferris Jul 18, 2024
669aa7d
test: add test for DeletePreviousTrackingIdentifierUseCase
alexandreferris Jul 18, 2024
1cf6287
chore: remove delete previous tracking identifier use case
alexandreferris Jul 19, 2024
34a3839
feat: add extra sealed interface for better handling of analytics ide…
alexandreferris Jul 19, 2024
00050ba
feat: add AnalyticsIdentifierManager to handle migration complete and…
alexandreferris Jul 19, 2024
a466cfa
chore: add missing extension from new sealed interface
alexandreferris Jul 19, 2024
96c5280
test: add tests for AnalyticsIdentifierManager
alexandreferris Jul 19, 2024
7a96b8b
chore: adjust detekt
alexandreferris Jul 19, 2024
f4f34e5
chore: remove unused import
alexandreferris Jul 19, 2024
0c68003
chore: rename user config current tracking identifier
alexandreferris Jul 22, 2024
35144c8
chore: add docs
alexandreferris Jul 22, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

package com.wire.kalium.logic.data.analytics

sealed interface AnalyticsIdentifierResult {
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved

sealed interface Enabled : AnalyticsIdentifierResult {
val identifier: String
}

data class NonExistingIdentifier(
override val identifier: String
) : Enabled

data class ExistingIdentifier(
override val identifier: String
) : Enabled

data class MigrationIdentifier(
override val identifier: String
) : Enabled

data object None : AnalyticsIdentifierResult
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ class KaliumLogger(
)

enum class ApplicationFlow {
SYNC, EVENT_RECEIVER, CONVERSATIONS, CONNECTIONS, MESSAGES, SEARCH, SESSION, REGISTER, CLIENTS, CALLING, ASSETS, LOCAL_STORAGE
SYNC, EVENT_RECEIVER, CONVERSATIONS, CONNECTIONS, MESSAGES, SEARCH, SESSION, REGISTER,
CLIENTS, CALLING, ASSETS, LOCAL_STORAGE, ANALYTICS
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ interface UserConfigRepository {
fun setShouldFetchE2EITrustAnchors(shouldFetch: Boolean)
fun getShouldFetchE2EITrustAnchor(): Boolean
suspend fun setTrackingIdentifier(identifier: String)
suspend fun getTrackingIdentifier(): String?
suspend fun observeTrackingIdentifier(): Flow<Either<StorageFailure, String>>
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved
suspend fun setPreviousTrackingIdentifier(identifier: String)
suspend fun getPreviousTrackingIdentifier(): String?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the previousTrackingIdentifier, how does it compare with getTrackingIdentifier?

Every time we setTrackingIdentifier(N) we setPreviousTrackingIdentifier(N-1)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we set the PreviouTrackingIdentifier when we receive a new TrackingIdentifier from remote (meaning a new device of the user was logged in somewhere else and we now need to use this new tracking identifier).

Both identifiers are kept until a proper analytics migration of the new identifier is done, then the previous identifier is deleted.

suspend fun deletePreviousTrackingIdentifier()
}

@Suppress("TooManyFunctions")
Expand Down Expand Up @@ -502,4 +507,25 @@ internal class UserConfigDataSource internal constructor(
userConfigDAO.setTrackingIdentifier(identifier = identifier)
}
}

override suspend fun getTrackingIdentifier(): String? =
userConfigDAO.getTrackingIdentifier()

override suspend fun observeTrackingIdentifier(): Flow<Either<StorageFailure, String>> =
userConfigDAO.observeTrackingIdentifier().wrapStorageRequest()

override suspend fun setPreviousTrackingIdentifier(identifier: String) {
wrapStorageRequest {
userConfigDAO.setPreviousTrackingIdentifier(identifier = identifier)
}
}

override suspend fun getPreviousTrackingIdentifier(): String? =
userConfigDAO.getPreviousTrackingIdentifier()

override suspend fun deletePreviousTrackingIdentifier() {
wrapStorageRequest {
userConfigDAO.deletePreviousTrackingIdentifier()
}
}
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ import com.wire.kalium.logic.di.MapperProvider
import com.wire.kalium.logic.di.PlatformUserStorageProperties
import com.wire.kalium.logic.di.RootPathsProvider
import com.wire.kalium.logic.di.UserStorageProvider
import com.wire.kalium.logic.feature.analytics.AnalyticsIdentifierManager
import com.wire.kalium.logic.feature.analytics.ObserveAnalyticsTrackingIdentifierStatusUseCase
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserver
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl
import com.wire.kalium.logic.feature.applock.MarkTeamAppLockStatusAsNotifiedUseCase
Expand Down Expand Up @@ -1309,7 +1311,8 @@ class UserSessionScope internal constructor(
private val dataTransferEventHandler: DataTransferEventHandler
get() = DataTransferEventHandlerImpl(
userId,
userConfigRepository
userConfigRepository,
userScopedLogger
)

private val applicationMessageHandler: ApplicationMessageHandler
Expand Down Expand Up @@ -1475,6 +1478,19 @@ class UserSessionScope internal constructor(
val observeLegalHoldStateForUser: ObserveLegalHoldStateForUserUseCase
get() = ObserveLegalHoldStateForUserUseCaseImpl(clientRepository)

val observeAnalyticsTrackingIdentifierStatus: ObserveAnalyticsTrackingIdentifierStatusUseCase
get() = ObserveAnalyticsTrackingIdentifierStatusUseCase(userConfigRepository, userScopedLogger)

val analyticsIdentifierManager: AnalyticsIdentifierManager
get() = AnalyticsIdentifierManager(
messages.messageSender,
userConfigRepository,
userId,
clientIdProvider,
selfConversationIdProvider,
userScopedLogger
)

suspend fun observeIfE2EIRequiredDuringLogin(): Flow<Boolean?> = clientRepository.observeIsClientRegistrationBlockedByE2EI()

val observeLegalHoldForSelfUser: ObserveLegalHoldStateForSelfUserUseCase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.analytics

import com.benasher44.uuid.uuid4
import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.cache.SelfConversationIdProvider
import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.data.id.CurrentClientIdProvider
import com.wire.kalium.logic.data.message.Message
import com.wire.kalium.logic.data.message.MessageContent
import com.wire.kalium.logic.data.message.MessageTarget
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.message.MessageSender
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.foldToEitherWhileRight
import com.wire.kalium.logic.kaliumLogger
import kotlinx.datetime.Clock

interface AnalyticsIdentifierManager {

suspend fun onMigrationComplete()
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved

suspend fun propagateTrackingIdentifier(identifier: String)
}

@Suppress("FunctionNaming", "LongParameterList")
internal fun AnalyticsIdentifierManager(
messageSender: MessageSender,
userConfigRepository: UserConfigRepository,
selfUserId: UserId,
selfClientIdProvider: CurrentClientIdProvider,
selfConversationIdProvider: SelfConversationIdProvider,
defaultLogger: KaliumLogger = kaliumLogger
) = object : AnalyticsIdentifierManager {

private val TAG = "AnalyticsIdentifierManager"
private val logger = defaultLogger.withFeatureId(KaliumLogger.Companion.ApplicationFlow.ANALYTICS)

override suspend fun onMigrationComplete() {
userConfigRepository.deletePreviousTrackingIdentifier()

logger.i("$TAG Previous Tracking Identifier deleted.")
}

override suspend fun propagateTrackingIdentifier(identifier: String) {
val messageContent = MessageContent.DataTransfer(
trackingIdentifier = MessageContent.DataTransfer.TrackingIdentifier(
identifier = identifier
)
)
selfClientIdProvider().flatMap { currentClientId ->
selfConversationIdProvider().flatMap { selfConversationIdList ->
selfConversationIdList.foldToEitherWhileRight(Unit) { selfConversationId, _ ->
val date = Clock.System.now()
val message = Message.Signaling(
id = uuid4().toString(),
content = messageContent,
conversationId = selfConversationId,
date = date,
senderUserId = selfUserId,
senderClientId = currentClientId,
status = Message.Status.Sent,
isSelfMessage = true,
expirationData = null
)

messageSender.sendMessage(
message = message,
messageTarget = MessageTarget.Conversation()
).also {
logger.i("$TAG Tracking Identifier propagated.")
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.analytics

import com.benasher44.uuid.uuid4
import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.data.analytics.AnalyticsIdentifierResult
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMapRightWithEither
import com.wire.kalium.logic.functional.isRight
import com.wire.kalium.logic.kaliumLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map

/**
* Use case that allows observing if the analytics tracking identifier
* changes, due to receiving a new identifier from another client
* or when it's user's first interaction with analytics.
*/
interface ObserveAnalyticsTrackingIdentifierStatusUseCase {
/**
* Use case [ObserveAnalyticsTrackingIdentifierStatusUseCase] operation
*
* @return a [AnalyticsIdentifierResult]
*/
suspend operator fun invoke(): Flow<AnalyticsIdentifierResult>
}

@Suppress("FunctionNaming")
internal fun ObserveAnalyticsTrackingIdentifierStatusUseCase(
userConfigRepository: UserConfigRepository,
defaultLogger: KaliumLogger = kaliumLogger,
) = object : ObserveAnalyticsTrackingIdentifierStatusUseCase {

private val TAG = "ObserveAnalyticsTrackingIdentifierStatusUseCase"
private val logger = defaultLogger.withFeatureId(KaliumLogger.Companion.ApplicationFlow.ANALYTICS)

override suspend fun invoke(): Flow<AnalyticsIdentifierResult> =
userConfigRepository
.observeTrackingIdentifier()
.distinctUntilChanged()
.flatMapRightWithEither { currentIdentifier: String ->
val result =
userConfigRepository.getPreviousTrackingIdentifier()?.let {
logger.i("$TAG Updating Tracking Identifier with migration value.")
AnalyticsIdentifierResult.MigrationIdentifier(
identifier = currentIdentifier
)
} ?: AnalyticsIdentifierResult.ExistingIdentifier(
identifier = currentIdentifier
).also {
logger.i("$TAG Updating Tracking Identifier with existing value.")
}

flowOf(Either.Right(result))
}.map {
// it's needed, otherwise it will be detected as Flow<Any>
if (it.isRight()) it.value as AnalyticsIdentifierResult
else {
userConfigRepository.getTrackingIdentifier()?.let { currentIdentifier: String ->
logger.i("$TAG Updating Tracking Identifier with existing value.")
AnalyticsIdentifierResult.ExistingIdentifier(
identifier = currentIdentifier
)
} ?: uuid4().toString().let { trackingIdentifier: String ->
logger.i("$TAG Generating new Tracking Identifier value.")
userConfigRepository.setTrackingIdentifier(
identifier = trackingIdentifier
)

AnalyticsIdentifierResult.NonExistingIdentifier(
identifier = trackingIdentifier
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.wire.kalium.logic.sync.receiver.handler

import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.configuration.UserConfigRepository
import com.wire.kalium.logic.data.message.Message
import com.wire.kalium.logic.data.message.MessageContent
Expand All @@ -32,21 +33,43 @@ internal interface DataTransferEventHandler {

internal class DataTransferEventHandlerImpl(
private val selfUserId: UserId,
private val userConfigRepository: UserConfigRepository
private val userConfigRepository: UserConfigRepository,
logger: KaliumLogger = kaliumLogger,
) : DataTransferEventHandler {

private val logger = logger.withFeatureId(KaliumLogger.Companion.ApplicationFlow.ANALYTICS)

override suspend fun handle(
message: Message.Signaling,
messageContent: MessageContent.DataTransfer
) {
kaliumLogger.d("MessageContent.DataTransfer | with Identifier : ${messageContent.trackingIdentifier?.identifier} |end|")
// DataTransfer from another user or null tracking identifier shouldn't happen,
// If it happens, it's unnecessary
// and we can squish some performance by skipping it completely
// If it happens, it's unnecessary, and we can squish some performance by skipping it completely
if (message.senderUserId != selfUserId || messageContent.trackingIdentifier == null) return

userConfigRepository.setTrackingIdentifier(
identifier = messageContent.trackingIdentifier!!.identifier
)
val currentTrackingIdentifier = userConfigRepository.getTrackingIdentifier()
val isCurrentDifferentThanReceived = currentTrackingIdentifier != messageContent
.trackingIdentifier!!
.identifier

if (isCurrentDifferentThanReceived) {
currentTrackingIdentifier?.let {
userConfigRepository.setPreviousTrackingIdentifier(identifier = currentTrackingIdentifier)
logger.d("$TAG Moved Current Tracking Identifier to Previous")
}

userConfigRepository.setTrackingIdentifier(
identifier = requireNotNull(
messageContent
.trackingIdentifier
?.identifier
)
)
logger.d("$TAG Tracking Identifier Updated")
}
}

private companion object {
const val TAG = "DataTransferEventHandler"
}
}
Loading
Loading