Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions sdk/src/main/java/cloud/mindbox/mobile_sdk/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,14 @@ internal fun List<InApp>.sortByPriority(): List<InApp> {
return this.sortedByDescending { it.isPriority }
}

/**
* Tags to attach to operations emitted by this in-app: [InApp.tags] when non-empty and the tags
* feature is enabled, otherwise `null` (so Gson omits the `tags` key downstream). The feature-flag
* decision is taken by the caller and passed in as [isTagsFeatureEnabled].
*/
internal fun InApp.gatedTags(isTagsFeatureEnabled: Boolean): Map<String, String>? =
tags?.takeIf { it.isNotEmpty() && isTagsFeatureEnabled }

internal inline fun <T> Queue<T>.pollIf(predicate: (T) -> Boolean): T? {
return peek()?.takeIf(predicate)?.let { poll() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ internal fun DomainModule(
inAppTargetingErrorRepository = inAppTargetingErrorRepository,
inAppContentFetcher = inAppContentFetcher,
inAppRepository = inAppRepository,
inAppFailureTracker = inAppFailureTracker
inAppFailureTracker = inAppFailureTracker,
featureToggleManager = featureToggleManager
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ internal fun PresentationModule(
sessionStorageManager = sessionStorageManager,
userVisitManager = userVisitManager,
inAppMessageDelayedManager = inAppMessageDelayedManager,
timeProvider = timeProvider
timeProvider = timeProvider,
featureToggleManager = featureToggleManager
)
}
override val clipboardManager: ClipboardManager by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cloud.mindbox.mobile_sdk.models.operation.response.InAppConfigResponse
import java.util.concurrent.ConcurrentHashMap

internal const val SEND_INAPP_SHOW_ERROR_FEATURE = "MobileSdkShouldSendInAppShowError"
internal const val SEND_INAPP_TAGS_FEATURE = "MobileSdkShouldSendInAppTags"

internal class FeatureToggleManagerImpl : FeatureToggleManager {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ internal class InAppFailureTrackerImpl(
inAppRepository.sendInAppShowFailure(listOf(failure))
}

override fun sendFailure(inAppId: String, failureReason: FailureReason, errorDetails: String?) {
override fun sendFailure(
inAppId: String,
failureReason: FailureReason,
errorDetails: String?,
tags: Map<String, String>?
) {
val timestamp = Instant.ofEpochMilli(timeProvider.currentTimeMillis())
.convertToZonedDateTimeAtUTC()
.convertToString()
Expand All @@ -54,12 +59,18 @@ internal class InAppFailureTrackerImpl(
inAppId = inAppId,
failureReason = failureReason,
errorDetails = errorDetails?.take(COUNT_OF_CHARS_IN_ERROR_DETAILS),
dateTimeUtc = timestamp
dateTimeUtc = timestamp,
tags = tags
)
)
}

override fun collectFailure(inAppId: String, failureReason: FailureReason, errorDetails: String?) {
override fun collectFailure(
inAppId: String,
failureReason: FailureReason,
errorDetails: String?,
tags: Map<String, String>?
) {
val timestamp = Instant.ofEpochMilli(timeProvider.currentTimeMillis())
.convertToZonedDateTimeAtUTC()
.convertToString()
Expand All @@ -68,7 +79,8 @@ internal class InAppFailureTrackerImpl(
inAppId = inAppId,
failureReason = failureReason,
errorDetails = errorDetails?.take(COUNT_OF_CHARS_IN_ERROR_DETAILS),
dateTimeUtc = timestamp
dateTimeUtc = timestamp,
tags = tags
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package cloud.mindbox.mobile_sdk.inapp.data.managers
import cloud.mindbox.mobile_sdk.fromJsonTyped
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppSerializationManager
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppFailuresWrapper
import cloud.mindbox.mobile_sdk.models.operation.request.InAppHandleRequest
import cloud.mindbox.mobile_sdk.models.operation.request.InAppClickRequest
import cloud.mindbox.mobile_sdk.models.operation.request.InAppShowRequest
import cloud.mindbox.mobile_sdk.models.operation.request.InAppShowFailure
import cloud.mindbox.mobile_sdk.models.operation.request.InAppTargetingRequest
import cloud.mindbox.mobile_sdk.toJsonTyped
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
import cloud.mindbox.mobile_sdk.utils.loggingRunCatching
Expand All @@ -30,9 +31,20 @@ internal class InAppSerializationManagerImpl(private val gson: Gson) : InAppSeri
}
}

override fun serializeToInAppActionString(inAppId: String): String {
return loggingRunCatching("") {
gson.toJsonTyped<InAppHandleRequest>(InAppHandleRequest(inAppId = inAppId))
override fun serializeToInAppTargetingString(inAppId: String, tags: Map<String, String>?): String {
return loggingRunCatching(defaultValue = "") {
gson.toJsonTyped<InAppTargetingRequest>(InAppTargetingRequest(inAppId = inAppId, tags = tags))
}
}

override fun serializeToInAppClickActionString(inAppId: String, tags: Map<String, String>?): String {
return loggingRunCatching(defaultValue = "") {
gson.toJsonTyped<InAppClickRequest>(
InAppClickRequest(
inAppId = inAppId,
tags = tags,
)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ internal class InAppRepositoryImpl(
}
}

override fun sendInAppClicked(inAppId: String) {
inAppSerializationManager.serializeToInAppActionString(inAppId).apply {
override fun sendInAppClicked(inAppId: String, tags: Map<String, String>?) {
inAppSerializationManager.serializeToInAppClickActionString(inAppId, tags).apply {
if (isNotBlank()) {
MindboxEventManager.inAppClicked(
context,
Expand All @@ -120,8 +120,8 @@ internal class InAppRepositoryImpl(
}
}

override fun sendUserTargeted(inAppId: String) {
inAppSerializationManager.serializeToInAppActionString(inAppId).apply {
override fun sendUserTargeted(inAppId: String, tags: Map<String, String>?) {
inAppSerializationManager.serializeToInAppTargetingString(inAppId, tags).apply {
if (isNotBlank()) {
MindboxEventManager.sendUserTargeted(
context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ internal class InAppInteractorImpl(
inAppRepository.saveInAppStateChangeTime(timeStamp.toTimestamp())
}

override fun sendInAppClicked(inAppId: String) {
inAppRepository.sendInAppClicked(inAppId)
override fun sendInAppClicked(inAppId: String, tags: Map<String, String>?) {
inAppRepository.sendInAppClicked(inAppId, tags)
}

override suspend fun listenToTargetingEvents() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package cloud.mindbox.mobile_sdk.inapp.domain

import cloud.mindbox.mobile_sdk.Mindbox.logI
import cloud.mindbox.mobile_sdk.gatedTags
import cloud.mindbox.mobile_sdk.getErrorResponseBodyData
import cloud.mindbox.mobile_sdk.getImageUrl
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.asVolleyError
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.getProductFromTargetingData
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.getVolleyErrorDetails
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.shouldTrackImageDownloadError
import cloud.mindbox.mobile_sdk.inapp.domain.extensions.shouldTrackTargetingError
import cloud.mindbox.mobile_sdk.inapp.data.managers.SEND_INAPP_TAGS_FEATURE
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.InAppContentFetcher
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.FeatureToggleManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppFailureTracker
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppProcessingManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.InAppGeoRepository
Expand All @@ -27,20 +30,25 @@ internal class InAppProcessingManagerImpl(
private val inAppTargetingErrorRepository: InAppTargetingErrorRepository,
private val inAppContentFetcher: InAppContentFetcher,
private val inAppRepository: InAppRepository,
private val inAppFailureTracker: InAppFailureTracker
private val inAppFailureTracker: InAppFailureTracker,
private val featureToggleManager: FeatureToggleManager
) : InAppProcessingManager {

companion object {
private const val RESPONSE_STATUS_CUSTOMER_SEGMENTS_REQUIRE_CUSTOMER =
"CheckCustomerSegments requires customer"
}

private fun isTagsFeatureEnabled(): Boolean =
featureToggleManager.isEnabled(SEND_INAPP_TAGS_FEATURE)

override suspend fun chooseInAppToShow(
inApps: List<InApp>,
triggerEvent: InAppEventType,
): InApp? {
for (inApp in inApps) {
val data = getTargetingData(triggerEvent)
val tags = inApp.gatedTags(isTagsFeatureEnabled())
var isTargetingErrorOccurred = false
var isInAppContentFetched: Boolean? = null
var targetingCheck = false
Expand Down Expand Up @@ -105,7 +113,8 @@ internal class InAppProcessingManagerImpl(
inAppFailureTracker.sendFailure(
inAppId = inApp.id,
failureReason = FailureReason.UNKNOWN_ERROR,
errorDetails = "Unknown exception when checking target ${throwable.message}. ${throwable.cause?.getVolleyErrorDetails() ?: "volleyError=null"}"
errorDetails = "Unknown exception when checking target ${throwable.message}. ${throwable.cause?.getVolleyErrorDetails() ?: "volleyError=null"}",
tags = tags
)
throw throwable
}
Expand All @@ -125,13 +134,14 @@ internal class InAppProcessingManagerImpl(
}
mindboxLogD("loading and targeting fetching finished")
if (isTargetingErrorOccurred) return chooseInAppToShow(inApps, triggerEvent)
trackTargetingErrorIfAny(inApp, data)
trackTargetingErrorIfAny(inApp, data, tags)
if (isInAppContentFetched == false && targetingCheck) {
imageFetchError?.takeIf { it.shouldTrackImageDownloadError() }?.let { error ->
inAppFailureTracker.collectFailure(
inAppId = inApp.id,
failureReason = FailureReason.IMAGE_DOWNLOAD_FAILED,
errorDetails = (error.message ?: "Image loading error") + "\n Url is ${inApp.form.variants.first().getImageUrl()}"
errorDetails = (error.message ?: "Image loading error") + "\n Url is ${inApp.form.variants.first().getImageUrl()}",
tags = tags
)
}
}
Expand Down Expand Up @@ -184,7 +194,10 @@ internal class InAppProcessingManagerImpl(
if (isTargetingErrorOccurred) return sendTargetedInApp(inApp, triggerEvent)
if (inApp.targeting.checkTargeting(data)) {
logI("InApp with id = ${inApp.id} sends targeting by event $triggerEvent")
inAppRepository.sendUserTargeted(inAppId = inApp.id)
inAppRepository.sendUserTargeted(
inAppId = inApp.id,
tags = inApp.gatedTags(isTagsFeatureEnabled()),
)
}
}

Expand All @@ -210,7 +223,7 @@ internal class InAppProcessingManagerImpl(
mindboxLogW("Error fetching customer segmentations", error)
}

private fun trackTargetingErrorIfAny(inApp: InApp, data: TargetingData) {
private fun trackTargetingErrorIfAny(inApp: InApp, data: TargetingData, tags: Map<String, String>?) {
when {
inApp.targeting.hasSegmentationNode() &&
inAppSegmentationRepository.getCustomerSegmentationFetched() == CustomerSegmentationFetchStatus.SEGMENTATION_FETCH_ERROR -> {
Expand All @@ -219,7 +232,8 @@ internal class InAppProcessingManagerImpl(
inAppFailureTracker.collectFailure(
inAppId = inApp.id,
failureReason = FailureReason.CUSTOMER_SEGMENT_REQUEST_FAILED,
errorDetails = errorDetails
errorDetails = errorDetails,
tags = tags
)
}
return
Expand All @@ -232,7 +246,8 @@ internal class InAppProcessingManagerImpl(
inAppFailureTracker.collectFailure(
inAppId = inApp.id,
failureReason = FailureReason.GEO_TARGETING_FAILED,
errorDetails = errorDetails
errorDetails = errorDetails,
tags = tags
)
}
return
Expand All @@ -246,7 +261,8 @@ internal class InAppProcessingManagerImpl(
inAppFailureTracker.collectFailure(
inAppId = inApp.id,
failureReason = FailureReason.PRODUCT_SEGMENT_REQUEST_FAILED,
errorDetails = errorDetails
errorDetails = errorDetails,
tags = tags
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ private fun parseOperationBody(operationBody: String?): Pair<String, String>? =
internal fun InAppFailureTracker.sendPresentationFailure(
inAppId: String,
errorDescription: String,
throwable: Throwable? = null
throwable: Throwable? = null,
tags: Map<String, String>? = null
) {
val errorDetails = when {
throwable != null -> "$errorDescription: ${throwable.message ?: "Unknown error"}"
Expand All @@ -92,15 +93,17 @@ internal fun InAppFailureTracker.sendPresentationFailure(
sendFailure(
inAppId = inAppId,
failureReason = FailureReason.PRESENTATION_FAILED,
errorDetails = errorDetails
errorDetails = errorDetails,
tags = tags
)
}

internal fun InAppFailureTracker.sendFailureWithContext(
inAppId: String,
failureReason: FailureReason,
errorDescription: String,
throwable: Throwable? = null
throwable: Throwable? = null,
tags: Map<String, String>? = null
) {
val errorDetails = when {
throwable != null -> "$errorDescription: ${throwable.message ?: "Unknown error"}"
Expand All @@ -110,14 +113,16 @@ internal fun InAppFailureTracker.sendFailureWithContext(
sendFailure(
inAppId = inAppId,
failureReason = failureReason,
errorDetails = errorDetails
errorDetails = errorDetails,
tags = tags
)
}

internal inline fun <T> InAppFailureTracker.executeWithFailureTracking(
inAppId: String,
failureReason: FailureReason,
errorDescription: String,
tags: Map<String, String>? = null,
crossinline onFailure: () -> Unit = {},
block: () -> T
): Result<T> {
Expand All @@ -126,7 +131,8 @@ internal inline fun <T> InAppFailureTracker.executeWithFailureTracking(
inAppId = inAppId,
failureReason = failureReason,
errorDescription = errorDescription,
throwable = throwable
throwable = throwable,
tags = tags
)
onFailure()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal interface InAppInteractor {
tags: Map<String, String>?
)

fun sendInAppClicked(inAppId: String)
fun sendInAppClicked(inAppId: String, tags: Map<String, String>?)

suspend fun fetchMobileConfig()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ internal interface InAppFailureTracker {
fun sendFailure(
inAppId: String,
failureReason: FailureReason,
errorDetails: String?
errorDetails: String?,
tags: Map<String, String>? = null
)

fun collectFailure(
inAppId: String,
failureReason: FailureReason,
errorDetails: String?
errorDetails: String?,
tags: Map<String, String>? = null
)

fun sendCollectedFailures()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ internal interface InAppSerializationManager {

fun serializeToInAppShownActionString(inAppId: String, timeToDisplay: String, tags: Map<String, String>?): String

fun serializeToInAppActionString(inAppId: String): String
fun serializeToInAppTargetingString(inAppId: String, tags: Map<String, String>?): String

fun serializeToInAppClickActionString(inAppId: String, tags: Map<String, String>?): String

fun serializeToInAppShowFailuresString(inAppShowFailures: List<InAppShowFailure>): String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ internal interface InAppRepository {

fun sendInAppShown(inAppId: String, timeToDisplay: String, tags: Map<String, String>?)

fun sendInAppClicked(inAppId: String)
fun sendInAppClicked(inAppId: String, tags: Map<String, String>?)

fun sendUserTargeted(inAppId: String)
fun sendUserTargeted(inAppId: String, tags: Map<String, String>?)

fun sendInAppShowFailure(failures: List<InAppShowFailure>)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ internal data class InAppTypeWrapper<out T : InAppType>(
val inAppType: T,
val inAppActionCallbacks: InAppActionCallbacks,
val onRenderStart: () -> Unit,
val tags: Map<String, String>? = null,
)

internal fun interface OnInAppClick {
Expand Down
Loading