Skip to content

Commit 80c8de7

Browse files
committed
create evaluation event context (inc. dd context) in evaluation event
1 parent 45fa637 commit 80c8de7

File tree

3 files changed

+68
-26
lines changed

3 files changed

+68
-26
lines changed

features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/EvaluationEventsProcessor.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ internal class EvaluationEventsProcessor(
7373
fun processEvaluation(
7474
flagKey: String,
7575
context: EvaluationContext,
76-
rumContext: DDContext,
76+
ddContext: DDContext,
7777
variantKey: String?,
7878
allocationKey: String?,
7979
reason: String?,
@@ -87,14 +87,14 @@ internal class EvaluationEventsProcessor(
8787
variantKey = variantKey,
8888
allocationKey = allocationKey,
8989
targetingKey = context.targetingKey,
90-
rumViewId = rumContext.viewId,
90+
rumViewId = ddContext.viewId,
9191
errorCode = errorCode
9292
)
9393

9494
@Suppress("UnsafeThirdPartyFunctionCall") // Only throws if null is passed
9595
val existing = aggregationMap.putIfAbsent(
9696
key,
97-
AggregationStats(timestamp, context, reason, errorMessage)
97+
AggregationStats(timestamp, context, ddContext, reason, errorMessage)
9898
)
9999

100100
// Pre-existing stats object found, record evaluation

features/dd-sdk-android-flags/src/main/kotlin/com/datadog/android/flags/internal/aggregation/AggregationStats.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package com.datadog.android.flags.internal.aggregation
88

99
import com.datadog.android.flags.model.BatchedFlagEvaluations
1010
import com.datadog.android.flags.model.EvaluationContext
11+
import com.datadog.android.flags.model.BatchedFlagEvaluations.Context1 as EvaluationEventContext
1112

1213
/**
1314
* Aggregation statistics for evaluation logging.
@@ -17,17 +18,20 @@ import com.datadog.android.flags.model.EvaluationContext
1718
* - First/last timestamps
1819
* - Last error message (updated on each evaluation)
1920
* - Runtime default usage
21+
* - Datadog context (service, RUM application/view)
2022
*
2123
* Thread Safe
2224
*
2325
* @param firstTimestamp the timestamp of the first evaluation
2426
* @param context the evaluation context
27+
* @param ddContext the Datadog context (service, RUM application/view)
2528
* @param reason the resolution reason (null for error evaluations, non-null for success evaluations)
2629
* @param errorMessage optional error message (detailed, for logging)
2730
*/
2831
internal class AggregationStats(
2932
firstTimestamp: Long,
3033
private val context: EvaluationContext,
34+
private val ddContext: DDContext,
3135
private val reason: String?,
3236
errorMessage: String?
3337
) {
@@ -90,14 +94,30 @@ internal class AggregationStats(
9094
snapshotMessage = lastErrorMessage
9195
}
9296

97+
// Build context with Datadog-specific information
98+
val eventContext = EvaluationEventContext(
99+
evaluation = null, // Evaluation context reserved for future use
100+
dd = BatchedFlagEvaluations.Dd(
101+
service = ddContext.service,
102+
rum = ddContext.applicationId?.let { appId ->
103+
BatchedFlagEvaluations.Rum(
104+
application = BatchedFlagEvaluations.Application(id = appId),
105+
view = ddContext.viewName?.let { viewName ->
106+
BatchedFlagEvaluations.View(url = viewName)
107+
}
108+
)
109+
}
110+
)
111+
)
112+
93113
return BatchedFlagEvaluations.FlagEvaluation(
94114
timestamp = snapshotFirst,
95115
flag = BatchedFlagEvaluations.Identifier(flagKey),
96116
variant = aggregationKey.variantKey?.let { BatchedFlagEvaluations.Identifier(it) },
97117
allocation = aggregationKey.allocationKey?.let { BatchedFlagEvaluations.Identifier(it) },
98118
targetingRule = null, // Not applicable
99119
targetingKey = aggregationKey.targetingKey,
100-
context = null, // Context logging reserved for future use
120+
context = eventContext,
101121
error = snapshotMessage?.let { BatchedFlagEvaluations.Error(message = it) },
102122
evaluationCount = snapshotCount.toLong(),
103123
firstEvaluation = snapshotFirst,

features/dd-sdk-android-flags/src/test/kotlin/com/datadog/android/flags/internal/aggregation/AggregationStatsTest.kt

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,32 @@ internal class AggregationStatsTest {
4646
@StringForgery
4747
lateinit var fakeFlagName: String
4848

49+
@StringForgery
50+
lateinit var fakeService: String
51+
52+
@StringForgery
53+
lateinit var fakeApplicationId: String
54+
55+
@StringForgery
56+
lateinit var fakeViewId: String
57+
58+
@StringForgery
59+
lateinit var fakeViewName: String
60+
4961
private lateinit var fakeContext: EvaluationContext
5062
private lateinit var fakeData: UnparsedFlag
5163
private lateinit var fakeAggregationKey: AggregationKey
64+
private lateinit var fakeDDContext: DDContext
5265

5366
@BeforeEach
5467
fun `set up`() {
5568
fakeContext = EvaluationContext(targetingKey = fakeTargetingKey)
69+
fakeDDContext = DDContext(
70+
service = fakeService,
71+
applicationId = fakeApplicationId,
72+
viewId = fakeViewId,
73+
viewName = fakeViewName
74+
)
5675
fakeData = PrecomputedFlag(
5776
variationType = "boolean",
5877
variationValue = fakeValue,
@@ -76,7 +95,7 @@ internal class AggregationStatsTest {
7695
@Test
7796
fun `M increment count W recordEvaluation()`() {
7897
// Given
79-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
98+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
8099

81100
// When
82101
stats.recordEvaluation(fakeTimestamp + 1000, null)
@@ -90,7 +109,7 @@ internal class AggregationStatsTest {
90109
@Test
91110
fun `M update timestamps W recordEvaluation() and toEvaluationEvent()`() {
92111
// Given
93-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
112+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
94113
val laterTimestamp = fakeTimestamp + 5000
95114

96115
// When
@@ -106,7 +125,7 @@ internal class AggregationStatsTest {
106125
@Test
107126
fun `M preserve first evaluation timestamp W recordEvaluation() { multiple calls }`() {
108127
// Given
109-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
128+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
110129

111130
// When
112131
repeat(10) { index ->
@@ -124,7 +143,7 @@ internal class AggregationStatsTest {
124143
val errorMessage1 = "First error: ${forge.anAlphabeticalString()}"
125144
val errorMessage2 = "Second error: ${forge.anAlphabeticalString()}"
126145
val errorMessage3 = "Third error: ${forge.anAlphabeticalString()}"
127-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, errorMessage1)
146+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, errorMessage1)
128147

129148
// When
130149
stats.recordEvaluation(fakeTimestamp + 1000, errorMessage2)
@@ -143,7 +162,7 @@ internal class AggregationStatsTest {
143162
@Test
144163
fun `M create event with correct fields W toEvaluationEvent() { successful match }`() {
145164
// Given
146-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
165+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
147166

148167
// When
149168
val event = stats.toEvaluationEvent(fakeFlagName, fakeAggregationKey)
@@ -160,7 +179,10 @@ internal class AggregationStatsTest {
160179
assertThat(event.runtimeDefaultUsed).isFalse()
161180
assertThat(event.error).isNull()
162181
assertThat(event.targetingRule).isNull()
163-
assertThat(event.context).isNull()
182+
assertThat(event.context).isNotNull()
183+
assertThat(event.context?.dd?.service).isEqualTo(fakeService)
184+
assertThat(event.context?.dd?.rum?.application?.id).isEqualTo(fakeApplicationId)
185+
assertThat(event.context?.dd?.rum?.view?.url).isEqualTo(fakeViewName)
164186
}
165187

166188
@Test
@@ -175,7 +197,7 @@ internal class AggregationStatsTest {
175197
extraLogging = JSONObject(),
176198
reason = ResolutionReason.DEFAULT.name
177199
)
178-
val defaultStats = AggregationStats(fakeTimestamp, fakeContext, defaultData.reason, null)
200+
val defaultStats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, defaultData.reason, null)
179201
val keyWithoutVariant = fakeAggregationKey.copy(variantKey = null, allocationKey = null)
180202

181203
// When - DEFAULT reason
@@ -188,7 +210,7 @@ internal class AggregationStatsTest {
188210

189211
// Given - ERROR reason (reason = null, no flag data)
190212
val errorMessage = forge.anAlphabeticalString()
191-
val errorStats = AggregationStats(fakeTimestamp, fakeContext, null, errorMessage)
213+
val errorStats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, null, errorMessage)
192214
val keyWithError = fakeAggregationKey.copy(
193215
variantKey = null,
194216
allocationKey = null,
@@ -206,7 +228,7 @@ internal class AggregationStatsTest {
206228
@Test
207229
fun `M set runtime default false W toEvaluationEvent() { MATCHED or TARGETING_MATCH reason }`() {
208230
// Given - MATCHED reason
209-
val matchedStats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
231+
val matchedStats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
210232

211233
// When
212234
val matchedEvent = matchedStats.toEvaluationEvent(fakeFlagName, fakeAggregationKey)
@@ -224,7 +246,7 @@ internal class AggregationStatsTest {
224246
extraLogging = JSONObject(),
225247
reason = ResolutionReason.TARGETING_MATCH.name
226248
)
227-
val targetingStats = AggregationStats(fakeTimestamp, fakeContext, targetingData.reason, null)
249+
val targetingStats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, targetingData.reason, null)
228250

229251
// When
230252
val targetingEvent = targetingStats.toEvaluationEvent(fakeFlagName, fakeAggregationKey)
@@ -246,7 +268,7 @@ internal class AggregationStatsTest {
246268
extraLogging = JSONObject(),
247269
reason = unrecognizedReason
248270
)
249-
val stats = AggregationStats(fakeTimestamp, fakeContext, unrecognizedData.reason, null)
271+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, unrecognizedData.reason, null)
250272

251273
// When
252274
val event = stats.toEvaluationEvent(fakeFlagName, fakeAggregationKey)
@@ -259,7 +281,7 @@ internal class AggregationStatsTest {
259281
fun `M handle error message W toEvaluationEvent() { error present or absent }`(forge: Forge) {
260282
// Given - error provided
261283
val errorMessage = forge.anAlphabeticalString()
262-
val statsWithError = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, errorMessage)
284+
val statsWithError = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, errorMessage)
263285

264286
// When
265287
val eventWithError = statsWithError.toEvaluationEvent(fakeFlagName, fakeAggregationKey)
@@ -268,7 +290,7 @@ internal class AggregationStatsTest {
268290
assertThat(eventWithError.error?.message).isEqualTo(errorMessage)
269291

270292
// Given - no error
271-
val statsWithoutError = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
293+
val statsWithoutError = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
272294

273295
// When
274296
val eventWithoutError = statsWithoutError.toEvaluationEvent(fakeFlagName, fakeAggregationKey)
@@ -280,7 +302,7 @@ internal class AggregationStatsTest {
280302
@Test
281303
fun `M use aggregation key fields W toEvaluationEvent() { key overrides data }`(forge: Forge) {
282304
// Given
283-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
305+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
284306
val keyWithDifferentValues = AggregationKey(
285307
flagKey = forge.anAlphabeticalString(),
286308
variantKey = forge.anAlphabeticalString(),
@@ -301,7 +323,7 @@ internal class AggregationStatsTest {
301323
@Test
302324
fun `M handle null variant and allocation W toEvaluationEvent() { key has null fields }`() {
303325
// Given
304-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
326+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
305327

306328
// When - null variant
307329
val keyWithNullVariant = fakeAggregationKey.copy(variantKey = null)
@@ -325,7 +347,7 @@ internal class AggregationStatsTest {
325347
@Test
326348
fun `M handle concurrent recordEvaluation calls W recordEvaluation() { multiple threads }`() {
327349
// Given
328-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
350+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
329351
val threadCount = 10
330352
val executionsPerThread = 100
331353
val expectedCount = threadCount * executionsPerThread + 1 // +1 for initial construction
@@ -359,7 +381,7 @@ internal class AggregationStatsTest {
359381
@Test
360382
fun `M produce consistent snapshot W toEvaluationEvent() { called during concurrent updates }`() {
361383
// Given
362-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
384+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
363385
val updateThreadCount = 5
364386
val snapshotThreadCount = 5
365387
val executionsPerThread = 100
@@ -408,7 +430,7 @@ internal class AggregationStatsTest {
408430
@Test
409431
fun `M handle concurrent error message updates W recordEvaluation() { multiple threads }`(forge: Forge) {
410432
// Given
411-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
433+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
412434
val threadCount = 10
413435
val executionsPerThread = 50
414436

@@ -449,7 +471,7 @@ internal class AggregationStatsTest {
449471
fun `M maintain correct range W recordEvaluation() { out of order arrival }`() {
450472
// Given - initial timestamp at 5000ms
451473
val initialTimestamp = 5000L
452-
val stats = AggregationStats(initialTimestamp, fakeContext, fakeData.reason, null)
474+
val stats = AggregationStats(initialTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
453475

454476
// When - events arrive out of order
455477
stats.recordEvaluation(10000L, null) // Late event
@@ -466,7 +488,7 @@ internal class AggregationStatsTest {
466488
@Test
467489
fun `M handle same timestamp W recordEvaluation() { multiple evaluations at same time }`() {
468490
// Given
469-
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeData.reason, null)
491+
val stats = AggregationStats(fakeTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
470492

471493
// When - multiple evaluations at exact same timestamp
472494
repeat(5) {
@@ -483,7 +505,7 @@ internal class AggregationStatsTest {
483505
@Test
484506
fun `M handle large time jumps W recordEvaluation() { forward and backward }`() {
485507
// Given - test both forward and backward large jumps
486-
val stats = AggregationStats(50000L, fakeContext, fakeData.reason, null)
508+
val stats = AggregationStats(50000L, fakeContext, fakeDDContext, fakeData.reason, null)
487509

488510
// When - large time jump forward (e.g., NTP sync, time zone change)
489511
val futureTimestamp = 1000000000L
@@ -508,7 +530,7 @@ internal class AggregationStatsTest {
508530
fun `M handle concurrent out-of-order evaluations W recordEvaluation() { thread safety }`() {
509531
// Given
510532
val initialTimestamp = 50000L
511-
val stats = AggregationStats(initialTimestamp, fakeContext, fakeData.reason, null)
533+
val stats = AggregationStats(initialTimestamp, fakeContext, fakeDDContext, fakeData.reason, null)
512534
val threadCount = 10
513535
val executionsPerThread = 100
514536

0 commit comments

Comments
 (0)