Skip to content

Commit 6ce4960

Browse files
feat: Support exporter metadata in Android provider (#2944)
* feat: Support exporter metadata in Android provider Signed-off-by: Thomas Poignant <[email protected]> * adding test Signed-off-by: Thomas Poignant <[email protected]> --------- Signed-off-by: Thomas Poignant <[email protected]>
1 parent 95f13c2 commit 6ce4960

File tree

7 files changed

+112
-4
lines changed

7 files changed

+112
-4
lines changed

openfeature/providers/kotlin-provider/gofeatureflag-kotlin-provider/src/main/java/org/gofeatureflag/openfeature/bean/GoFeatureFlagOptions.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ data class GoFeatureFlagOptions(
4747
* when calling the evaluation API.
4848
* default: 1000 ms
4949
*/
50-
val flushIntervalMs: Long = 300000
50+
val flushIntervalMs: Long = 300000,
51+
52+
/**
53+
* (optional) exporter metadata is a set of key-value that will be added to the metadata when calling the
54+
* exporter API. All those informations will be added to the event produce by the exporter.
55+
*
56+
* ‼️Important: If you are using a GO Feature Flag relay proxy before version v1.41.0, the information
57+
* of this field will not be added to your feature events.
58+
*/
59+
val exporterMetadata: Map<String, Any> = emptyMap(),
5160
)
5261

openfeature/providers/kotlin-provider/gofeatureflag-kotlin-provider/src/main/java/org/gofeatureflag/openfeature/controller/GoFeatureFlagApi.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ class GoFeatureFlagApi(private val options: GoFeatureFlagOptions) {
5252
}
5353

5454
val mediaType = "application/json".toMediaTypeOrNull()
55-
val requestBody = gson.toJson(Events(events)).toRequestBody(mediaType)
55+
56+
val metadata = options.exporterMetadata.toMutableMap()
57+
metadata["provider"] = "android"
58+
metadata["openfeature"] = true
59+
val requestBody = gson.toJson(Events(events, metadata)).toRequestBody(mediaType)
5660
val reqBuilder = okhttp3.Request.Builder()
5761
.url(urlBuilder.build())
5862
.post(requestBody)

openfeature/providers/kotlin-provider/gofeatureflag-kotlin-provider/src/main/java/org/gofeatureflag/openfeature/hook/Events.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ package org.gofeatureflag.openfeature.hook
33

44
data class Events(
55
val events: List<Event>?,
6-
val meta: Map<String, String> = mapOf("provider" to "android", "openfeature" to "true")
6+
val meta: Map<String, Any> = emptyMap()
77
)

openfeature/providers/kotlin-provider/gofeatureflag-kotlin-provider/src/test/java/org/gofeatureflag/openfeature/GoFeatureFlagProviderTest.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,59 @@ class GoFeatureFlagProviderTest {
176176
val got2 = Gson().fromJson(recordedRequest2.body.readUtf8(), Events::class.java)
177177
assertEquals(3, got2.events?.size)
178178
}
179+
180+
@Test
181+
fun `should call the hook and send metadata`() {
182+
val jsonFilePath =
183+
javaClass.classLoader?.getResource("org.gofeatureflag.openfeature.ofrep/valid_api_response.json")?.file
184+
val jsonString = String(Files.readAllBytes(Paths.get(jsonFilePath)))
185+
mockWebServer!!.enqueue(MockResponse().setBody(jsonString).setResponseCode(200))
186+
mockWebServer!!.enqueue(MockResponse().setBody("{}").setResponseCode(200))
187+
mockWebServer!!.enqueue(MockResponse().setBody("{}").setResponseCode(200))
188+
val options =
189+
GoFeatureFlagOptions(
190+
endpoint = mockWebServer!!.url("/").toString(),
191+
flushIntervalMs = 100,
192+
pollingIntervalInMillis = 10000,
193+
exporterMetadata = mapOf("device" to "Pixel 4", "appVersion" to "1.0.0")
194+
)
195+
196+
val provider = GoFeatureFlagProvider(options)
197+
val ctx = ImmutableContext(targetingKey = "123")
198+
runBlocking {
199+
OpenFeatureAPI.setProviderAndWait(
200+
provider = provider,
201+
dispatcher = Dispatchers.IO,
202+
initialContext = ctx
203+
)
204+
}
205+
206+
val client = OpenFeatureAPI.getClient()
207+
client.getStringValue("title-flag", "default")
208+
client.getStringValue("title-flag", "default")
209+
client.getStringValue("title-flag", "default")
210+
client.getStringValue("title-flag", "default")
211+
client.getStringValue("title-flag", "default")
212+
client.getStringValue("title-flag", "default")
213+
Thread.sleep(1000)
214+
client.getStringValue("title-flag", "default")
215+
client.getStringValue("title-flag", "default")
216+
client.getStringValue("title-flag", "default")
217+
Thread.sleep(1000)
218+
mockWebServer!!.takeRequest()
219+
val recordedRequest: RecordedRequest = mockWebServer!!.takeRequest()
220+
val got = Gson().fromJson(recordedRequest.body.readUtf8(), Events::class.java)
221+
assertEquals(6, got.events?.size)
222+
assertEquals("Pixel 4", got.meta["device"])
223+
assertEquals("1.0.0", got.meta["appVersion"])
224+
assertEquals("android", got.meta["provider"])
225+
assertEquals(true, got.meta["openfeature"])
226+
val recordedRequest2: RecordedRequest = mockWebServer!!.takeRequest()
227+
val got2 = Gson().fromJson(recordedRequest2.body.readUtf8(), Events::class.java)
228+
assertEquals(3, got2.events?.size)
229+
assertEquals("Pixel 4", got2.meta["device"])
230+
assertEquals("1.0.0", got2.meta["appVersion"])
231+
assertEquals("android", got2.meta["provider"])
232+
assertEquals(true, got2.meta["openfeature"])
233+
}
179234
}

openfeature/providers/kotlin-provider/gofeatureflag-kotlin-provider/src/test/java/org/gofeatureflag/openfeature/controller/GoFeatureFlagApiTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,24 @@ class GoFeatureFlagApiTest {
140140
val got = recordedRequest.body.readUtf8()
141141
JSONAssert.assertEquals(want, got, false)
142142
}
143+
144+
@Test
145+
fun `should have a valid body request when using exporter metadata`(): Unit = runBlocking {
146+
mockWebServer!!.enqueue(MockResponse().setBody("{}").setResponseCode(200))
147+
val api =
148+
GoFeatureFlagApi(
149+
GoFeatureFlagOptions(
150+
endpoint = mockWebServer!!.url("/").toString(),
151+
apiKey = "my-api-key",
152+
exporterMetadata = mapOf("appVersion" to "1.0.0", "device" to "Pixel 4")
153+
)
154+
)
155+
api.postEventsToDataCollector(defaultEventList)
156+
val recordedRequest: RecordedRequest = mockWebServer!!.takeRequest()
157+
val jsonFilePath =
158+
javaClass.classLoader?.getResource("org/gofeatureflag/openfeature/hook/valid_result_metadata.json")?.file
159+
val want = File(jsonFilePath.toString()).readText(Charsets.UTF_8)
160+
val got = recordedRequest.body.readUtf8()
161+
JSONAssert.assertEquals(want, got, false)
162+
}
143163
}

openfeature/providers/kotlin-provider/gofeatureflag-kotlin-provider/src/test/resources/org/gofeatureflag/openfeature/hook/valid_result.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
],
1414
"meta": {
1515
"provider": "android",
16-
"openfeature": "true"
16+
"openfeature": true
1717
}
1818
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"events": [
3+
{
4+
"contextKind": "contextKind",
5+
"creationDate": 1721650841,
6+
"key": "flag-1",
7+
"kind": "feature",
8+
"userKey": "981f2662-1fb4-4732-ac6d-8399d9205aa9",
9+
"value": true,
10+
"default": false,
11+
"variation": "enabled"
12+
}
13+
],
14+
"meta": {
15+
"provider": "android",
16+
"openfeature": true,
17+
"appVersion": "1.0.0",
18+
"device": "Pixel 4"
19+
}
20+
}

0 commit comments

Comments
 (0)