Skip to content

Generate Kotlin type safe accessors for Optimizely experiments and features

License

Notifications You must be signed in to change notification settings

patxibocos/poetimizely

Repository files navigation

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:

  • optimizelyProjectId (Long): id of the Optimizely project to grab experiments and features from.
  • optimizelyToken (String): Optimizely personal access token. See Personal token authentication.
  • packageName (String): package where the code will be placed. The expected format is your.destination.package.

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)