Home

Awesome

codecov CI poetimizely-core poetimizely-gradle-plugin poetimizely-maven-plugin

What is poetimizely โ“

poetimizely is a library to generate type safe accessors for Optimizely experiments and features. Given a Project ID and a token it will generate classes for every experiment + variations and features + variables.

Setup โš™

โ„น๏ธ Before editing the build file, there are three properties required to configure explained below:

Gradle ๐Ÿ˜

Kotlin DSL (build.gradle.kts)

plugins {
  id("io.github.patxibocos.poetimizely") version "1.0.5"
}

poetimizely {
    optimizelyProjectId = $OPTIMIZELY_PROJECT_ID
    optimizelyToken = $PERSONAL_ACCESS_TOKEN
    packageName = $PACKAGE_NAME
}

Groovy (build.gradle)

plugins {
  id "io.github.patxibocos.poetimizely" version "1.0.5"
}

poetimizely {
    optimizelyProjectId = $OPTIMIZELY_PROJECT_ID
    optimizelyToken = $PERSONAL_ACCESS_TOKEN
    packageName = $PACKAGE_NAME
}

Maven ๐Ÿ•Š๏ธ

pom.xml

<build>
    <plugins>
        <plugin>
            <groupId>io.github.patxibocos</groupId>
            <artifactId>poetimizely-maven-plugin</artifactId>
            <version>1.0.5</version>
            <configuration>
                <optimizelyProjectId>$OPTIMIZELY_PROJECT_ID</optimizelyProjectId>
                <optimizelyToken>$PERSONAL_ACCESS_TOKEN</optimizelyToken>
                <packageName>$PACKAGE_NAME</packageName>
            </configuration>
        </plugin>
    </plugins>
</build>

Usage ๐Ÿ“‹

After the plugin has been setup, a new Gradle task / Maven goal named poetimize will be available. In order to run it successfully, both Optimizely project id and token must be valid.

For Gradle projects run:

./gradlew poetimize

or with Maven:

./mvnw poetimizely:poetimize

This will generate all the code based on the experiments (and its variants) and features defined for the given Optimizely project.

Experiments ๐Ÿงช

For each of the experiments, a new object will be generated. And for the set of variations for each of the experiments an enum class will be also created.

An extension function for the Optimizely class is also available that brings the type safety:

when (optimizely.getVariationForExperiment(Experiments.ExampleExperiment, userId)) {
    EXAMPLE_VARIATION -> TODO() 
    null -> TODO()
}

Features ๐Ÿ’ก

Kotlin objects will also be generated for features. For the set of variables that each of the features may have, a property is added to the object.

To check whether a feature is enabled an extension function is provided:

if (optimizely.isFeatureEnabled(Features.ExampleFeature, userId)) {
    TODO()
}

and also for getting feature variables values:

val booleanVariable: Boolean? = optimizely.getFeatureVariable(Features.ExampleFeature.exampleBooleanVariable)
val stringVariable: String? = optimizely.getFeatureVariable(Features.ExampleFeature.exampleStringVariable)
val doubleVariable: Double? = optimizely.getFeatureVariable(Features.ExampleFeature.exampleDoubleVariable)
val intVariable: Int? = optimizely.getFeatureVariable(Features.ExampleFeature.exampleIntVariable)

A look at the generated code ๐Ÿ‘€

After running the task there will be two new classes generated in the given packageName:

Experiments.kt

interface BaseVariation {
    val key: String
}

fun getAllExperiments(): List<Experiments<out BaseVariation>> =
    listOf(Experiments.ExampleExperiment)

fun <V : BaseVariation> Optimizely.getVariationForExperiment(
    experiment: Experiments<V>,
    userId: String,
    attributes: Map<String, Any> = emptyMap()
): V? {
    val variation = this.activate(experiment.key, userId, attributes)
    return experiment.variations.find { it.key == variation?.key }
}

enum class ExampleExperimentVariations : BaseVariation {
    EXAMPLE_VARIATION {
        override val key: String = "example-variation"
    }
}

sealed class Experiments<V : BaseVariation>(
    val key: String,
    val variations: Array<V>
) {
    object ExampleExperiment : Experiments<ExampleExperimentVariations> (
        "example-experiment",
        ExampleExperimentVariations.values()
    )
}

Features.kt

class FeatureVariable<T>(
    val featureKey: String,
    val variableKey: String
)

sealed class Features(
    val key: String
) {
    object ExampleFeature("example-feature") {
        val exampleVariable: FeatureVariable<Boolean> = FeatureVariable("example-feature", "example-variable")
    } 
}

public fun getAllFeatures(): List<Features> = listOf(Features.ExampleFeature)

fun Optimizely.isFeatureEnabled(
    feature: Features,
    userId: String,
    attributes: Map<String, Any> = emptyMap()
): Boolean = this.isFeatureEnabled(feature.key, userId, attributes)

fun Optimizely.getFeatureVariable(
    variable: FeatureVariable<Boolean>,
    userId: String,
    attributes: Map<String, Any> = emptyMap()
): Boolean? =
    this.getFeatureVariableBoolean(variable.featureKey, variable.variableKey, userId, attributes)

fun Optimizely.getFeatureVariable(
    variable: FeatureVariable<String>,
    userId: String,
    attributes: Map<String, Any> = emptyMap()
): String? =
    this.getFeatureVariableString(variable.featureKey, variable.variableKey, userId, attributes)

fun Optimizely.getFeatureVariable(
    variable: FeatureVariable<Double>,
    userId: String,
    attributes: Map<String, Any> = emptyMap()
): Double? =
    this.getFeatureVariableDouble(variable.featureKey, variable.variableKey, userId, attributes)

fun Optimizely.getFeatureVariable(
    variable: FeatureVariable<Int>,
    userId: String,
    attributes: Map<String, Any> = emptyMap()
): Int? =
    this.getFeatureVariableInteger(variable.featureKey, variable.variableKey, userId, attributes)