Skip to content
Permalink
Browse files

Issue #12: Implement simple API for getting experiment metadata

Closes #12: Implement simple API for getting experiment metadata

Fretboard withExperiment refactored to more idiomatic Kotlin

Mention that Fretboard getExperiment method will return the Experiment even if the user is not part of it

Warn that for now we decided to support primitive types only as payload
  • Loading branch information...
fercarcedo committed Jun 21, 2018
1 parent 1f86832 commit 36b17e1e2d8d87992dab5b3e8d72e463032a827a
@@ -15,7 +15,8 @@ data class Experiment(
val description: String? = null,
val match: Matcher? = null,
val bucket: Bucket? = null,
val lastModified: Long? = null
val lastModified: Long? = null,
val payload: ExperimentPayload? = null
) {
data class Matcher(
val language: String? = null,
@@ -16,9 +16,17 @@ internal class ExperimentEvaluator(private val regionProvider: RegionProvider? =
experimentDescriptor: ExperimentDescriptor,
experiments: List<Experiment>,
userBucket: Int = getUserBucket(context)
): Boolean {
val experiment = experiments.firstOrNull { it.id == experimentDescriptor.id } ?: return false
return isInBucket(userBucket, experiment) && matches(context, experiment)
): Experiment? {
val experiment = getExperiment(experimentDescriptor, experiments) ?: return null
return if (isInBucket(userBucket, experiment) && matches(context, experiment)) {
experiment
} else {
null
}
}

fun getExperiment(descriptor: ExperimentDescriptor, experiments: List<Experiment>): Experiment? {
return experiments.firstOrNull { it.id == descriptor.id }
}

private fun matches(context: Context, experiment: Experiment): Boolean {
@@ -0,0 +1,102 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.service.fretboard

/**
* Class which represents an experiment associated data
*/
class ExperimentPayload {
private val valuesMap = HashMap<String, Any>()

/**
* Puts a value into the payload
*
* @param key key
* @param value value to put under the key
*/
fun put(key: String, value: Any) {
valuesMap[key] = value
}

/**
* Gets a value from the payload
*
* @param key key
*
* @return value under the specified key
*/
fun get(key: String): Any? {
return valuesMap[key]
}

/**
* Gets all the payload keys
*
* @return set of payload keys
*/
fun getKeys(): Set<String> {
return valuesMap.keys.toSet()
}

/**
* Gets a value from the payload as a list of Boolean
*
* @param key key
*
* @return value under the specified key as a list of Boolean
*/
@Suppress("UNCHECKED_CAST")
fun getBooleanList(key: String): List<Boolean>? {
return get(key) as List<Boolean>?
}

/**
* Gets a value from the payload as a list of Int
*
* @param key key
*
* @return value under the specified key as a list of Int
*/
@Suppress("UNCHECKED_CAST")
fun getIntList(key: String): List<Int>? {
return get(key) as List<Int>?
}

/**
* Gets a value from the payload as a list of Long
*
* @param key key
*
* @return value under the specified key as a list of Long
*/
@Suppress("UNCHECKED_CAST")
fun getLongList(key: String): List<Long>? {
return get(key) as List<Long>?
}

/**
* Gets a value from the payload as a list of Double
*
* @param key key
*
* @return value under the specified key as a list of Double
*/
@Suppress("UNCHECKED_CAST")
fun getDoubleList(key: String): List<Double>? {
return get(key) as List<Double>?
}

/**
* Gets a value from the payload as a list of String
*
* @param key key
*
* @return value under the specified key as a list of String
*/
@Suppress("UNCHECKED_CAST")
fun getStringList(key: String): List<String>? {
return get(key) as List<String>?
}
}
@@ -58,7 +58,7 @@ class Fretboard(
* @return true if the user is part of the specified experiment, false otherwise
*/
fun isInExperiment(context: Context, descriptor: ExperimentDescriptor): Boolean {
return evaluator.evaluate(context, descriptor, experiments)
return evaluator.evaluate(context, descriptor, experiments) != null
}

/**
@@ -69,8 +69,17 @@ class Fretboard(
* @param block block of code to be executed if the user is part of the experiment
*/
fun withExperiment(context: Context, descriptor: ExperimentDescriptor, block: (Experiment) -> Unit) {
if (evaluator.evaluate(context, descriptor, experiments)) {
block(experiments.first { it.id == descriptor.id })
}
evaluator.evaluate(context, descriptor, experiments)?.let { block(it) }
}

/**
* Gets the metadata associated with the specified experiment, even if the user is not part of it
*
* @param descriptor descriptor of the experiment
*
* @return metadata associated with the experiment
*/
fun getExperiment(descriptor: ExperimentDescriptor): Experiment? {
return evaluator.getExperiment(descriptor, experiments)
}
}
@@ -34,12 +34,15 @@ class JSONExperimentParser {
val bucket = if (bucketsObject != null) {
Experiment.Bucket(bucketsObject.tryGetInt(MAX_KEY), bucketsObject.tryGetInt(MIN_KEY))
} else null
val payloadJson: JSONObject? = jsonObject.optJSONObject(PAYLOAD_KEY)
val payload = if (payloadJson != null) jsonToPayload(payloadJson) else null
return Experiment(jsonObject.getString(ID_KEY),
jsonObject.tryGetString(NAME_KEY),
jsonObject.tryGetString(DESCRIPTION_KEY),
matcher,
bucket,
jsonObject.tryGetLong(LAST_MODIFIED_KEY))
jsonObject.tryGetLong(LAST_MODIFIED_KEY),
payload)
}

/**
@@ -63,6 +66,37 @@ class JSONExperimentParser {
jsonObject.putIfNotNull(DESCRIPTION_KEY, experiment.description)
jsonObject.put(ID_KEY, experiment.id)
jsonObject.putIfNotNull(LAST_MODIFIED_KEY, experiment.lastModified)
jsonObject.putIfNotNull(PAYLOAD_KEY, payloadToJson(experiment.payload))
return jsonObject
}

private fun jsonToPayload(jsonObject: JSONObject): ExperimentPayload {
// For now we decided to support primitive types only
val payload = ExperimentPayload()
for (key in jsonObject.keys()) {
val value = jsonObject.get(key)
if (value !is JSONObject) {
when (value) {
is JSONArray -> payload.put(key, value.toList<Any>())
else -> payload.put(key, value)
}
}
}
return payload
}

private fun payloadToJson(payload: ExperimentPayload?): JSONObject? {
if (payload == null) {
return null
}
val jsonObject = JSONObject()
for (key in payload.getKeys()) {
val value = payload.get(key)
when (value) {
is List<*> -> jsonObject.put(key, value.toJsonArray())
else -> jsonObject.put(key, value)
}
}
return jsonObject
}

@@ -124,5 +158,6 @@ class JSONExperimentParser {
private const val NAME_KEY = "name"
private const val DESCRIPTION_KEY = "description"
private const val LAST_MODIFIED_KEY = "last_modified"
private const val PAYLOAD_KEY = "payload"
}
}
@@ -7,8 +7,8 @@ package mozilla.components.service.fretboard
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -49,12 +49,12 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 21))
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 69))
assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 19))
assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 70))
assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 71))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 21))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 69))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 19))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 70))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 71))
}

@Test
@@ -87,9 +87,9 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
`when`(context.packageName).thenReturn("test")
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
@@ -122,7 +122,7 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
experiment = Experiment(
"testid",
"testexperiment",
@@ -141,7 +141,7 @@ class ExperimentEvaluatorTest {
20
),
1528916183)
assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
@@ -174,7 +174,7 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))

experiment = Experiment(
"testid",
@@ -195,7 +195,7 @@ class ExperimentEvaluatorTest {
),
1528916183)

assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
@@ -228,10 +228,10 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))

packageInfo.versionName = "other.version"
assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
@@ -264,7 +264,7 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))

experiment = Experiment(
"testid",
@@ -285,7 +285,7 @@ class ExperimentEvaluatorTest {
),
1528916183)

assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
@@ -318,7 +318,7 @@ class ExperimentEvaluatorTest {
`when`(context.packageManager).thenReturn(packageManager)

val evaluator = ExperimentEvaluator()
assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))

experiment = Experiment(
"testid",
@@ -339,7 +339,7 @@ class ExperimentEvaluatorTest {
),
1528916183)

assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
@@ -377,22 +377,22 @@ class ExperimentEvaluatorTest {
}
})

assertTrue(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNotNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))

evaluator = ExperimentEvaluator(object : RegionProvider {
override fun getRegion(): String {
return "ESP"
}
})

assertFalse(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
assertNull(evaluator.evaluate(context, ExperimentDescriptor("testid"), listOf(experiment), 20))
}

@Test
fun testEvaluateNoExperimentSameAsDescriptor() {
val savedExperiment = Experiment("wrongid")
val descriptor = ExperimentDescriptor("testid")
val context = mock(Context::class.java)
assertFalse(ExperimentEvaluator().evaluate(context, descriptor, listOf(savedExperiment), 20))
assertNull(ExperimentEvaluator().evaluate(context, descriptor, listOf(savedExperiment), 20))
}
}
Oops, something went wrong.

0 comments on commit 36b17e1

Please sign in to comment.
You can’t perform that action at this time.