All Downloads are FREE. Search and download functionalities are using the official Maven repository.

assignment.AssignmentService.kt Maven / Gradle / Ivy

package com.amplitude.experiment.assignment

import com.amplitude.Amplitude
import com.amplitude.AmplitudeCallbacks
import com.amplitude.Event
import com.amplitude.experiment.LocalEvaluationMetrics
import com.amplitude.experiment.util.LocalEvaluationMetricsWrapper
import com.amplitude.experiment.util.wrapMetrics
import org.json.JSONObject

private object FlagType {
    const val RELEASE = "release"
    const val EXPERIMENT = "experiment"
    const val MUTUAL_EXCLUSION_GROUP = "mutual-exclusion-group"
    const val HOLDOUT_GROUP = "holdout-group"
    const val RELEASE_GROUP = "release-group"
}

internal interface AssignmentService {
    fun track(assignment: Assignment)
}

class EventTrackingException(
    event: Event,
    status: Int,
    message: String?
) : Exception(
    "Failed to track event to amplitude: event=${event.eventType}, status=$status, msg=$message"
)

internal class AmplitudeAssignmentService(
    private val amplitude: Amplitude,
    private val assignmentFilter: AssignmentFilter,
    private val metrics: LocalEvaluationMetrics = LocalEvaluationMetricsWrapper(),
) : AssignmentService {

    init {
        amplitude.setCallbacks(object : AmplitudeCallbacks() {
            override fun onLogEventServerResponse(event: Event?, status: Int, message: String?) {
                if (event == null) return
                if (status != 200) {
                    metrics.onAssignmentEventFailure(EventTrackingException(event, status, message))
                }
            }
        })
    }

    override fun track(assignment: Assignment) {
        metrics.onAssignment()
        if (assignmentFilter.shouldTrack(assignment)) {
            wrapMetrics(
                metric = metrics::onAssignmentEvent,
                failure = metrics::onAssignmentEventFailure,
            ) {
                amplitude.logEvent(assignment.toAmplitudeEvent())
            }
        } else {
            metrics.onAssignmentFilter()
        }
    }
}

internal fun Assignment.toAmplitudeEvent(): Event {
    val event = Event(
        "[Experiment] Assignment",
        this.user.userId,
        this.user.deviceId
    )
    if (!user.groups.isNullOrEmpty()) {
        event.groups = JSONObject(user.groups)
    }
    event.eventProperties = JSONObject().apply {
        for ((flagKey, variant) in [email protected]) {
            val version = variant.metadata?.get("flagVersion")
            val segmentName = variant.metadata?.get("segmentName")
            val details = "v$version rule:$segmentName"
            put("$flagKey.variant", variant.key)
            put("$flagKey.details", details)
        }
    }
    event.userProperties = JSONObject().apply {
        val set = JSONObject()
        val unset = JSONObject()
        for ((flagKey, variant) in [email protected]) {
            val flagType = variant.metadata?.get("flagType") as? String
            val default = variant.metadata?.get("default") as? Boolean ?: false
            if (flagType == FlagType.MUTUAL_EXCLUSION_GROUP) {
                // Dont set user properties for mutual exclusion groups.
                continue
            } else if (default) {
                unset.put("[Experiment] $flagKey", "-")
            } else {
                set.put("[Experiment] $flagKey", variant.key)
            }
        }
        put("\$set", set)
        put("\$unset", unset)
    }
    event.insertId = "${this.user.userId} ${this.user.deviceId} ${this.canonicalize().hashCode()} ${this.timestamp / DAY_MILLIS}"
    return event
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy