Skip to content

Commit

Permalink
feat(Provider): Add support for Tracking from OpenFeature (#190)
Browse files Browse the repository at this point in the history
* feat(Provider): add support for tracking in the Openfeature provider

* fix: replace print with logger
  • Loading branch information
nicklasl authored Jan 27, 2025
1 parent a2b10f1 commit 97c763c
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class EventSenderEngineImpl(
}
private val exceptionHandler by lazy {
CoroutineExceptionHandler { _, e ->
print(e.message)
debugLogger?.logMessage(message = "EventSenderEngine error: $e", isWarning = true)
}
}

Expand Down
1 change: 1 addition & 0 deletions Provider/api/Provider.api
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public final class com/spotify/confidence/openfeature/ConfidenceFeatureProvider
public fun observe ()Lkotlinx/coroutines/flow/Flow;
public fun onContextSet (Ldev/openfeature/sdk/EvaluationContext;Ldev/openfeature/sdk/EvaluationContext;)V
public fun shutdown ()V
public fun track (Ljava/lang/String;Ldev/openfeature/sdk/EvaluationContext;Ldev/openfeature/sdk/TrackingEventDetails;)V
}

public final class com/spotify/confidence/openfeature/ConfidenceFeatureProvider$Companion {
Expand Down
2 changes: 1 addition & 1 deletion Provider/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
}

object Versions {
const val openFeatureSDK = "0.3.0"
const val openFeatureSDK = "0.3.2"
const val okHttp = "4.10.0"
const val kotlinxSerialization = "1.6.0"
const val coroutines = "1.7.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import dev.openfeature.sdk.Hook
import dev.openfeature.sdk.ProviderEvaluation
import dev.openfeature.sdk.ProviderMetadata
import dev.openfeature.sdk.Reason
import dev.openfeature.sdk.TrackingEventDetails
import dev.openfeature.sdk.Value
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
Expand Down Expand Up @@ -130,6 +131,10 @@ class ConfidenceFeatureProvider private constructor(
return generateEvaluation(key, defaultValue)
}

override fun track(trackingEventName: String, context: EvaluationContext?, details: TrackingEventDetails?) {
confidence.track(trackingEventName, details?.toConfidenceValue() ?: emptyMap())
}

private fun <T> generateEvaluation(
key: String,
defaultValue: T
Expand Down Expand Up @@ -166,6 +171,16 @@ class ConfidenceFeatureProvider private constructor(
}
}

private fun TrackingEventDetails.toConfidenceValue(): Map<String, ConfidenceValue> = mapOf(
"value" to (this.value?.toConfidenceValue() ?: ConfidenceValue.Null)
) + this.structure.asMap().mapValues { it.value.toConfidenceValue() }

private fun Number.toConfidenceValue(): ConfidenceValue = when (this) {
is Int -> ConfidenceValue.Integer(this)
is Double -> ConfidenceValue.Double(this)
else -> ConfidenceValue.Null
}

internal fun Value.toConfidenceValue(): ConfidenceValue = when (this) {
is Value.Structure -> ConfidenceValue.Struct(structure.mapValues { it.value.toConfidenceValue() })
is Value.Boolean -> ConfidenceValue.Boolean(this.boolean)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ package com.spotify.confidence.openfeature
import android.content.Context
import com.spotify.confidence.ConfidenceFactory
import dev.openfeature.sdk.ImmutableContext
import dev.openfeature.sdk.ImmutableStructure
import dev.openfeature.sdk.OpenFeatureAPI
import dev.openfeature.sdk.Reason
import dev.openfeature.sdk.TrackingEventDetails
import dev.openfeature.sdk.Value
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.exceptions.ErrorCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -176,5 +181,61 @@ class ProviderIntegrationTest {

assertEquals(4, evaluationDetails.value.asStructure()?.getOrDefault("my-integer", Value.Integer(-1))?.asInteger())
}

@Test
fun testEventTracking() = runTest {
val testDispatcher = StandardTestDispatcher(testScheduler)
val eventsHandler = EventHandler(Dispatchers.IO).apply {
publish(OpenFeatureEvents.ProviderStale)
}
val cacheDir = mockContext.getDir("events", Context.MODE_PRIVATE)
assertTrue(cacheDir.isDirectory)
assertTrue(cacheDir.listFiles().isEmpty())
val mockConfidence = ConfidenceFactory.create(mockContext, clientSecret, dispatcher = testDispatcher)

OpenFeatureAPI.setProvider(
ConfidenceFeatureProvider.create(
confidence = mockConfidence,
initialisationStrategy = InitialisationStrategy.ActivateAndFetchAsync,
eventHandler = eventsHandler
),
ImmutableContext(
targetingKey = UUID.randomUUID().toString(),
attributes = mutableMapOf(
"user" to Value.Structure(
mapOf(
"country" to Value.String("SE")
)
)
)
)
)
runBlocking {
awaitProviderReady(eventsHandler = eventsHandler)
}

assertEquals(1, cacheDir.listFiles()?.size)
assertEquals(0, cacheDir.listFiles()?.first()?.readLines()?.size)

OpenFeatureAPI.getClient().track("MyEventName", TrackingEventDetails(33.0, ImmutableStructure("key" to Value.String("value"))))
testScheduler.advanceUntilIdle()
val lines = cacheDir.listFiles()?.first()?.readLines() ?: emptyList()
assertEquals(1, lines.size)
val jsonString = lines.first()
assertTrue(jsonString.contains("\"eventDefinition\":\"MyEventName\""))
println(lines.first())
assertTrue(jsonString.contains("\"payload\":{\"value\":{\"double\":33.0},\"key\":{\"string\":\"value\"}"))
val regex = Regex(
"\"context\":\\{\"map\":\\{\"visitor_id\":\\{\"string\":\"[a-f0-9\\-]+\"}," +
"\"targeting_key\":\\{\"string\":\"[a-f0-9\\-]+\"}," +
"\"user\":\\{\"map\":\\{\"country\":\\{\"string\":\"SE\"}}}}}"
)
assertTrue(
"Expected the context map to match the regex for visitor_id and targeting_key. Actual JSON:\n$jsonString",
regex.containsMatchIn(jsonString)
)
}

private val flagsFileName = "confidence_flags_cache.json"
private val eventsFileName = "confidence_flags_cache.json"
}

0 comments on commit 97c763c

Please sign in to comment.