Skip to content

Commit

Permalink
feat: add exp key to variant and exposure (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
bgiori authored Jun 1, 2023
1 parent a62ee1a commit f25e458
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal class ConnectorExposureTrackingProvider(
eventProperties = mapOf(
"flag_key" to exposure.flagKey,
"variant" to exposure.variant,
"experiment_key" to exposure.experimentKey,
).filterNull()
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ internal class DefaultExperimentClient internal constructor(
val event = OldExposureEvent(exposedUser, key, variant, source)
// Track the exposure event if an analytics provider is set
if (source.isFallback() || variant.value == null) {
userSessionExposureTracker?.track(Exposure(key, null), exposedUser)
userSessionExposureTracker?.track(Exposure(key, null, variant.expKey), exposedUser)
analyticsProvider?.unsetUserProperty(event)
} else {
userSessionExposureTracker?.track(Exposure(key, variant.value), exposedUser)
userSessionExposureTracker?.track(Exposure(key, variant.value, variant.expKey), exposedUser)
analyticsProvider?.setUserProperty(event)
analyticsProvider?.track(event)
}
Expand Down
10 changes: 6 additions & 4 deletions sdk/src/main/java/com/amplitude/experiment/Experiment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ object Experiment {
* instance name.
*
* @param application The Android Application context
* @param apiKey The API key. This can be found in the Experiment settings and should not be null or empty.
* @param apiKey The API key. This can be found in the Experiment settings
* and should not be null or empty.
* @param config see [ExperimentConfig] for configuration options
*/
@JvmStatic
Expand Down Expand Up @@ -67,11 +68,12 @@ object Experiment {
* integrates with the installed and initialized instance of the amplitude
* analytics SDK.
*
* You must be using Amplitude-Android SDK version 2.36.0+ for this
* integration to work.
* You must be using Amplitude-Android SDK version 2.36.0+ or
* Amplitude-Kotlin 1.5.0+ for this integration to work.
*
* @param application The Android Application context
* @param apiKey The API key. This can be found in the Experiment settings and should not be null or empty.
* @param apiKey The API key. This can be found in the Experiment settings
* and should not be null or empty.
* @param config see [ExperimentConfig] for configuration options
*/
@JvmStatic
Expand Down
1 change: 1 addition & 0 deletions sdk/src/main/java/com/amplitude/experiment/Exposure.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ package com.amplitude.experiment
data class Exposure internal constructor(
val flagKey: String,
val variant: String?,
val experimentKey: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ interface ExposureTrackingProvider {
* "event_type": "$exposure",
* "event_properties": {
* "flag_key": "<flagKey>",
* "variant": "<variant>"
* "variant": "<variant>",
* "experiment_key": "<expKey>",
* }
* }
* ```
Expand All @@ -62,6 +63,7 @@ interface ExposureTrackingProvider {
* new Properties()
* .putValue("flag_key", exposureEvent.flagKey)
* .putValue("variant", exposureEvent.variant)
* .putValue("experiment_key", exposureEvent.experimentKey)
* );
* ```
*/
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/main/java/com/amplitude/experiment/Variant.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package com.amplitude.experiment

data class Variant @JvmOverloads constructor(
/**
* The value of the variant.
*/
@JvmField val value: String? = null,
/**
* The attached payload, if any.
*/
@JvmField val payload: Any? = null,
/**
* The experiment key. Used to distinguish two experiments associated with the same flag.
*/
@JvmField val expKey: String? = null,
) {

/**
Expand Down
9 changes: 8 additions & 1 deletion sdk/src/main/java/com/amplitude/experiment/util/Variant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ internal fun Variant.toJson(): String {
if (payload != null) {
jsonObject.put("payload", payload)
}
if (expKey != null) {
jsonObject.put("expKey", expKey)
}
} catch (e: JSONException) {
Logger.w("Error converting Variant to json string", e)
}
Expand Down Expand Up @@ -38,7 +41,11 @@ internal fun JSONObject?.toVariant(): Variant? {
has("payload") -> get("payload")
else -> null
}
Variant(value, payload)
val expKey = when {
has("expKey") -> getString("expKey")
else -> null
}
Variant(value, payload, expKey)
} catch (e: JSONException) {
Logger.w("Error parsing Variant from json string $this")
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ConnectorExposureTrackingProviderTest {

// Track event with variant

val exposureEvent1 = Exposure("test-key-1", "test")
val exposureEvent1 = Exposure("test-key-1", "test", null)
val expectedTrack1 = AnalyticsEvent("\$exposure", mapOf("flag_key" to "test-key-1", "variant" to "test"))

connectorExposureTrackingProvider.track(exposureEvent1)
Expand All @@ -41,7 +41,7 @@ class ConnectorExposureTrackingProviderTest {

// Track new flag key event with same variant

val exposureEvent2 = Exposure("test-key-2", "test")
val exposureEvent2 = Exposure("test-key-2", "test", null)
val expectedTrack2 = AnalyticsEvent("\$exposure", mapOf("flag_key" to "test-key-2", "variant" to "test"))

eventBridge.recentEvent = null
Expand All @@ -63,7 +63,7 @@ class ConnectorExposureTrackingProviderTest {

// Track event with variant

val exposureEvent1 = Exposure("test-key", "test")
val exposureEvent1 = Exposure("test-key", "test", null)
val expectedTrack1 = AnalyticsEvent("\$exposure", mapOf("flag_key" to "test-key", "variant" to "test"))

connectorExposureTrackingProvider.track(exposureEvent1)
Expand All @@ -78,7 +78,7 @@ class ConnectorExposureTrackingProviderTest {

// Track same flag key event with new variant

val exposureEvent2 = Exposure("test-key", "test2")
val exposureEvent2 = Exposure("test-key", "test2", null)
val expectedTrack2 = AnalyticsEvent("\$exposure", mapOf("flag_key" to "test-key", "variant" to "test2"))

eventBridge.recentEvent = null
Expand All @@ -94,7 +94,7 @@ class ConnectorExposureTrackingProviderTest {

// Track event with no variant

val exposureEvent3 = Exposure("test-key", null)
val exposureEvent3 = Exposure("test-key", null, null)
val expectedTrack3 = AnalyticsEvent("\$exposure", mapOf("flag_key" to "test-key"))

eventBridge.recentEvent = null
Expand Down
27 changes: 27 additions & 0 deletions sdk/src/test/java/com/amplitude/experiment/ExperimentClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -380,4 +380,31 @@ class ExperimentClientTest {
Assert.assertTrue(didExposureGetTracked)
Assert.assertTrue(didUserPropertyGetSet)
}

@Test
fun `test exposure through exposure tracking provider has experiment key from variant`() {
var didTrack = false
val exposureTrackingProvider = object : ExposureTrackingProvider {
override fun track(exposure: Exposure) {
Assert.assertEquals("flagKey", exposure.flagKey)
Assert.assertEquals("variant", exposure.variant)
Assert.assertEquals("experimentKey", exposure.experimentKey)
didTrack = true
}
}
val client = DefaultExperimentClient(
API_KEY,
ExperimentConfig(
debug = true,
exposureTrackingProvider = exposureTrackingProvider,
source = Source.INITIAL_VARIANTS,
initialVariants = mapOf("flagKey" to Variant("variant", null, "experimentKey"))
),
OkHttpClient(),
InMemoryStorage(),
Experiment.executorService,
)
client.variant("flagKey")
Assert.assertTrue(didTrack)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ class UserSessionExposureTrackerTest {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)

val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure)
}
Assert.assertEquals(exposure, provider.lastExposure)
Assert.assertEquals(1, provider.trackCount)

val exposure2 = Exposure("flag2", "variant")
val exposure2 = Exposure("flag2", "variant", null)
repeat(10) {
tracker.track(exposure2)
}
Expand All @@ -32,11 +32,11 @@ class UserSessionExposureTrackerTest {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)

val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure)
}
val exposure2 = Exposure("flag", null)
val exposure2 = Exposure("flag", null, null)
repeat(10) {
tracker.track(exposure2)
}
Expand All @@ -50,11 +50,11 @@ class UserSessionExposureTrackerTest {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)

val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure)
}
val exposure2 = Exposure("flag", "variant2")
val exposure2 = Exposure("flag", "variant2", null)
repeat(10) {
tracker.track(exposure2)
}
Expand All @@ -67,7 +67,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on user id change, null to value`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure)
}
Expand All @@ -82,7 +82,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on device id change, null to value`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure)
}
Expand All @@ -97,7 +97,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on user id change, value to null`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure, ExperimentUser(userId = "uid"))
}
Expand All @@ -112,7 +112,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on device id change, value to null`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure, ExperimentUser(deviceId = "did"))
}
Expand All @@ -127,7 +127,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on user id change, value to different value`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure, ExperimentUser(userId = "uid"))
}
Expand All @@ -142,7 +142,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on device id change, value to different value`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure, ExperimentUser(deviceId = "did"))
}
Expand All @@ -157,7 +157,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on user id and device id change, null to value`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure)
}
Expand All @@ -172,7 +172,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on user id and device id change, value to null`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure, ExperimentUser(userId = "uid", deviceId = "did"))
}
Expand All @@ -187,7 +187,7 @@ class UserSessionExposureTrackerTest {
fun `test track called again on user id and device id change, value to different value`() {
val provider = TestExposureTrackingProvider()
val tracker = UserSessionExposureTracker(provider)
val exposure = Exposure("flag", "variant")
val exposure = Exposure("flag", "variant", null)
repeat(10) {
tracker.track(exposure, ExperimentUser(userId = "uid", deviceId = "did"))
}
Expand Down
5 changes: 4 additions & 1 deletion sdk/src/test/java/com/amplitude/experiment/VariantTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ class VariantTest {
val jsonObject = JSONObject()
jsonObject.put("value", "value")
jsonObject.put("payload", "payload")
jsonObject.put("expKey", "expKey")
val variant = jsonObject.toVariant()
Assert.assertNotNull(variant)
Assert.assertEquals("value", variant!!.value)
Assert.assertEquals("payload", variant.payload)
Assert.assertEquals("expKey", variant.expKey)
}

@Test
Expand All @@ -45,9 +47,10 @@ class VariantTest {
@Test
fun `variant to json object`() {
run {
val variant = Variant("value", null)
val variant = Variant("value", null, "expKey")
val jsonObject = JSONObject()
jsonObject.put("value", "value")
jsonObject.put("expKey", "expKey")
Assert.assertEquals(jsonObject.toString(), variant.toJson())
}
}
Expand Down

0 comments on commit f25e458

Please sign in to comment.