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
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cloud.mindbox.mobile_sdk.abtests

import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository
import cloud.mindbox.mobile_sdk.inapp.domain.models.ABTest
import cloud.mindbox.mobile_sdk.logger.MindboxLog
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences

internal class InAppABTestLogic(
private val mixer: CustomerAbMixer,
private val repository: MobileConfigRepository,
): MindboxLog {

suspend fun getInAppsPool(allInApps: List<String>): Set<String> {
val abtests = repository.getABTests()
val uuid = MindboxPreferences.deviceUuid

if (abtests.isEmpty()) {
logI("Abtests is empty. Use all inApps.")
return allInApps.toSet()
}
if (allInApps.isEmpty()) {
return emptySet()
}

val inAppsForAbtest: List<Set<String>> = abtests.map { abtest ->

val hash = mixer.stringModulusHash(
identifier = uuid,
salt = abtest.salt.uppercase(),
)

logI("Mixer calculate hash $hash for abtest ${abtest.id} with salt ${abtest.salt} and deviceUuid $uuid")

val targetVariantInApps: MutableSet<String> = mutableSetOf()
val otherVariantInApps: MutableSet<String> = mutableSetOf()

abtest.variants.onEach { variant ->
val inApps: List<String> = when (variant.kind) {
ABTest.Variant.VariantKind.ALL -> allInApps
ABTest.Variant.VariantKind.CONCRETE -> variant.inapps
}
if ((variant.lower until variant.upper).contains(hash)) {
targetVariantInApps.addAll(inApps)
logI("Selected variant $variant for ${abtest.id}")
} else {
otherVariantInApps.addAll(inApps)
}
}

(targetVariantInApps + (allInApps - otherVariantInApps)).also { inappsSet ->
logI("For abtest ${abtest.id} determined $inappsSet")
}
}

if (inAppsForAbtest.isEmpty()) {
logW("No inApps after calculation abtests logic. InApp will not be shown.")
return setOf()
}

return if (inAppsForAbtest.size == 1) {
inAppsForAbtest.first()
} else {
getIntersectionForAllABTests(inAppsForAbtest)
}
}

private fun getIntersectionForAllABTests(inAppsForAbtest: List<Set<String>>): Set<String> =
inAppsForAbtest.reduce { acc, list -> acc.intersect(list) }

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cloud.mindbox.mobile_sdk.di.modules


import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixer
import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixerImpl
import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic
import cloud.mindbox.mobile_sdk.inapp.domain.InAppChoosingManagerImpl
import cloud.mindbox.mobile_sdk.inapp.domain.InAppEventManagerImpl
import cloud.mindbox.mobile_sdk.inapp.domain.InAppFilteringManagerImpl
Expand All @@ -26,7 +28,8 @@ internal fun DomainModule(
inAppSegmentationRepository = inAppSegmentationRepository,
inAppFilteringManager = inAppFilteringManager,
inAppEventManager = inAppEventManager,
inAppChoosingManager = inAppChoosingManager
inAppChoosingManager = inAppChoosingManager,
inAppABTestLogic = inAppABTestLogic,
)
}

Expand All @@ -44,6 +47,12 @@ internal fun DomainModule(
override val inAppFilteringManager: InAppFilteringManager
get() = InAppFilteringManagerImpl(inAppRepository = inAppRepository)

override val inAppABTestLogic: InAppABTestLogic
get() = InAppABTestLogic(
mixer = customerAbMixer,
repository = mobileConfigRepository
)

override val customerAbMixer: CustomerAbMixer
get() = CustomerAbMixerImpl()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cloud.mindbox.mobile_sdk.di.modules

import android.app.Application
import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixer
import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.data.mapper.InAppMapper
import cloud.mindbox.mobile_sdk.inapp.data.validators.ABTestValidator
Expand Down Expand Up @@ -87,6 +88,7 @@ internal interface DomainModule : MindboxModule {
val inAppEventManager: InAppEventManager
val inAppFilteringManager: InAppFilteringManager
val customerAbMixer: CustomerAbMixer
val inAppABTestLogic: InAppABTestLogic
}

internal interface ApiModule : MindboxModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ internal class InAppMapper {
salt = dto.salt!!,
variants = dto.variants?.map { variantDto ->
ABTest.Variant(
id = variantDto.id,
type = variantDto.objects!!.first().type!!,
kind = variantDto.objects.first().kind.enumValue(),
inapps = variantDto.objects.first().inapps ?: listOf(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ internal class VariantValidator : Validator<ABTestDto.VariantDto?> {
return false
}

if (item.id.isBlank()) {
mindboxLogW("The 'id' field can not be null or empty")
return false
}

if (item.modulus == null) {
mindboxLogW("The 'modulus' field can not be null")
return false
Expand All @@ -32,7 +37,6 @@ internal class VariantValidator : Validator<ABTestDto.VariantDto?> {
return false
}


if (item.objects.size != 1) {
mindboxLogW("The 'objects' field must be only one")
return false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cloud.mindbox.mobile_sdk.inapp.domain

import cloud.mindbox.mobile_sdk.InitializeLock
import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppChoosingManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppEventManager
Expand All @@ -11,7 +12,7 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfi
import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppType
import cloud.mindbox.mobile_sdk.inapp.domain.models.ProductSegmentationFetchStatus
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.MindboxLog
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.models.InAppEventType
import kotlinx.coroutines.flow.*
Expand All @@ -22,26 +23,31 @@ internal class InAppInteractorImpl(
private val inAppSegmentationRepository: InAppSegmentationRepository,
private val inAppFilteringManager: InAppFilteringManager,
private val inAppEventManager: InAppEventManager,
private val inAppChoosingManager: InAppChoosingManager
) : InAppInteractor {
private val inAppChoosingManager: InAppChoosingManager,
private val inAppABTestLogic: InAppABTestLogic,
) : InAppInteractor, MindboxLog {

override suspend fun processEventAndConfig(): Flow<InAppType> {
val inApps: List<InApp> = mobileConfigRepository.getInAppsSection().let { inApps ->
inAppFilteringManager.filterNotShownInApps(
inAppRepository.getShownInApps(),
inApps
)
}.also { unShownInApps ->
MindboxLoggerImpl.d(
this, "Filtered config has ${unShownInApps.size} inapps"
)
inAppSegmentationRepository.unShownInApps = unShownInApps
for (inApp in unShownInApps) {
for (operation in inApp.targeting.getOperationsSet()) {
inAppRepository.saveOperationalInApp(operation.lowercase(), inApp)
val inApps: List<InApp> = mobileConfigRepository.getInAppsSection()
.let { inApps ->
val inAppIds = inAppABTestLogic.getInAppsPool(inApps.map { it.id })
inAppFilteringManager.filterABTestsInApps(inApps, inAppIds).also { filteredInApps ->
logI("InApps after abtest logic ${filteredInApps.map { it.id }}")
}
}.let { inApps ->
inAppFilteringManager.filterNotShownInApps(
inAppRepository.getShownInApps(),
inApps
)
}.also { unShownInApps ->
logI("Filtered config has ${unShownInApps.size} inapps")
inAppSegmentationRepository.unShownInApps = unShownInApps
for (inApp in unShownInApps) {
for (operation in inApp.targeting.getOperationsSet()) {
inAppRepository.saveOperationalInApp(operation.lowercase(), inApp)
}
}
}
}

return inAppRepository.listenInAppEvents()
.filter { event -> inAppEventManager.isValidInAppEvent(event) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ internal data class ABTest(
val variants: List<Variant>,
) {
internal data class Variant(
val id: String,
val type: String,
val kind: VariantKind,
val lower: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,9 @@ internal fun Any.mindboxLogE(message: String, exception: Throwable? = null) = ex
MindboxLoggerImpl.e(this, message, exception)
} ?: MindboxLoggerImpl.e(this, message)

internal interface MindboxLog {
fun logD(message: String) = this.mindboxLogD(message)
fun logI(message: String) = this.mindboxLogI(message)
fun logW(message: String, exception: Throwable? = null) = this.mindboxLogW(message, exception)
fun logE(message: String, exception: Throwable? = null) = this.mindboxLogE(message, exception)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ internal data class ABTestDto(
val variants: List<VariantDto>?,
) {
internal data class VariantDto(
@SerializedName("id")
val id: String,
@SerializedName("modulus")
val modulus: ModulusDto?,
@SerializedName("objects")
Expand Down
Loading