Skip to content

Commit 7ada356

Browse files
committed
RUM-10088: Propagate has_replay flag to RumContext object
1 parent 7737285 commit 7ada356

File tree

2 files changed

+143
-34
lines changed
  • features/dd-sdk-android-rum/src

2 files changed

+143
-34
lines changed

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ internal open class RumViewScope(
126126
internal var version: Long = 1
127127
internal val customTimings: MutableMap<String, Long> = mutableMapOf()
128128
internal val featureFlags: MutableMap<String, Any?> = mutableMapOf()
129+
internal var hasReplay = false
129130

130131
internal var stopped: Boolean = false
131132

@@ -258,7 +259,7 @@ internal open class RumViewScope(
258259
viewType = type,
259260
viewTimestamp = eventTimestamp,
260261
viewTimestampOffset = serverTimeOffsetInMs,
261-
hasReplay = false
262+
hasReplay = hasReplay
262263
)
263264
}
264265

@@ -464,13 +465,13 @@ internal open class RumViewScope(
464465
// make a copy - by the time we iterate over it on another thread, it may already be changed
465466
val eventFeatureFlags = featureFlags.toMutableMap()
466467
val eventType = if (isFatal) EventType.CRASH else EventType.DEFAULT
468+
hasReplay = hasReplay || featuresContextResolver.resolveViewHasReplay(
469+
datadogContext,
470+
rumContext.viewId.orEmpty()
471+
)
467472

468473
sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer, eventType) {
469474
val user = datadogContext.userInfo
470-
val hasReplay = featuresContextResolver.resolveViewHasReplay(
471-
datadogContext,
472-
rumContext.viewId.orEmpty()
473-
)
474475
val syntheticsAttribute = if (
475476
rumContext.syntheticsTestId.isNullOrBlank() ||
476477
rumContext.syntheticsResultId.isNullOrBlank()
@@ -927,20 +928,18 @@ internal open class RumViewScope(
927928
)
928929
}
929930

931+
val currentViewId = rumContext.viewId.orEmpty()
932+
hasReplay = hasReplay || featuresContextResolver.resolveViewHasReplay(
933+
datadogContext,
934+
currentViewId
935+
)
936+
val sessionReplayRecordsCount = featuresContextResolver.resolveViewRecordsCount(
937+
datadogContext,
938+
currentViewId
939+
)
940+
930941
sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer, eventType) {
931-
val currentViewId = rumContext.viewId.orEmpty()
932942
val user = datadogContext.userInfo
933-
val hasReplay = featuresContextResolver.resolveViewHasReplay(
934-
datadogContext,
935-
currentViewId
936-
)
937-
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) { currentRumContext ->
938-
currentRumContext[RumContext.HAS_REPLAY] = hasReplay
939-
}
940-
val sessionReplayRecordsCount = featuresContextResolver.resolveViewRecordsCount(
941-
datadogContext,
942-
currentViewId
943-
)
944943
val replayStats = ViewEvent.ReplayStats(recordsCount = sessionReplayRecordsCount)
945944
val syntheticsAttribute = if (
946945
rumContext.syntheticsTestId.isNullOrBlank() ||
@@ -1245,12 +1244,12 @@ internal open class RumViewScope(
12451244
val timestamp = event.eventTime.timestamp + serverTimeOffsetInMs
12461245
val isFrozenFrame = event.durationNs > FROZEN_FRAME_THRESHOLD_NS
12471246
slowFramesListener?.onAddLongTask(event.durationNs)
1247+
hasReplay = hasReplay || featuresContextResolver.resolveViewHasReplay(
1248+
datadogContext,
1249+
rumContext.viewId.orEmpty()
1250+
)
12481251
sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) {
12491252
val user = datadogContext.userInfo
1250-
val hasReplay = featuresContextResolver.resolveViewHasReplay(
1251-
datadogContext,
1252-
rumContext.viewId.orEmpty()
1253-
)
12541253
val syntheticsAttribute = if (
12551254
rumContext.syntheticsTestId.isNullOrBlank() ||
12561255
rumContext.syntheticsResultId.isNullOrBlank()

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ internal class RumViewScopeTest {
191191
@Forgery
192192
lateinit var fakeDatadogContext: DatadogContext
193193

194+
@Forgery
195+
lateinit var fakeViewType: RumViewType
196+
194197
var fakeSourceViewEvent: ViewEvent.ViewEventSource? = null
195198
var fakeSourceErrorEvent: ErrorEvent.ErrorEventSource? = null
196199
var fakeSourceActionEvent: ActionEvent.ActionEventSource? = null
@@ -364,12 +367,17 @@ internal class RumViewScopeTest {
364367
val context = testedScope.getRumContext()
365368

366369
// Then
367-
assertThat(context.actionId).isNull()
370+
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
371+
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
368372
assertThat(context.viewId).isEqualTo(testedScope.viewId)
369373
assertThat(context.viewName).isEqualTo(fakeKey.name)
370374
assertThat(context.viewUrl).isEqualTo(fakeUrl)
371-
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
372-
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
375+
assertThat(context.viewType).isEqualTo(fakeViewType)
376+
assertThat(context.viewTimestamp)
377+
.isEqualTo(fakeEventTime.timestamp + fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
378+
assertThat(context.viewTimestampOffset).isEqualTo(fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
379+
assertThat(context.actionId).isNull()
380+
assertThat(context.hasReplay).isEqualTo(false)
373381
}
374382

375383
@Test
@@ -382,11 +390,16 @@ internal class RumViewScopeTest {
382390

383391
// Then
384392
assertThat(context.actionId).isEqualTo(fakeActionId)
393+
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
394+
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
385395
assertThat(context.viewId).isEqualTo(testedScope.viewId)
386396
assertThat(context.viewName).isEqualTo(fakeKey.name)
387397
assertThat(context.viewUrl).isEqualTo(fakeUrl)
388-
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
389-
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
398+
assertThat(context.viewType).isEqualTo(fakeViewType)
399+
assertThat(context.viewTimestamp)
400+
.isEqualTo(fakeEventTime.timestamp + fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
401+
assertThat(context.viewTimestampOffset).isEqualTo(fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
402+
assertThat(context.hasReplay).isEqualTo(false)
390403
}
391404

392405
@Test
@@ -403,19 +416,116 @@ internal class RumViewScopeTest {
403416
val updatedContext = testedScope.getRumContext()
404417

405418
// Then
406-
assertThat(context.actionId).isNull()
419+
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
420+
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
407421
assertThat(context.viewId).isEqualTo(initialViewId)
408422
assertThat(context.viewName).isEqualTo(fakeKey.name)
409423
assertThat(context.viewUrl).isEqualTo(fakeUrl)
410-
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
411-
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
424+
assertThat(context.viewType).isEqualTo(fakeViewType)
425+
assertThat(context.viewTimestamp)
426+
.isEqualTo(fakeEventTime.timestamp + fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
427+
assertThat(context.viewTimestampOffset).isEqualTo(fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
428+
assertThat(context.actionId).isNull()
429+
assertThat(context.hasReplay).isEqualTo(false)
412430

413-
assertThat(updatedContext.actionId).isNull()
431+
assertThat(updatedContext.applicationId).isEqualTo(fakeParentContext.applicationId)
432+
assertThat(updatedContext.sessionId).isEqualTo(newSessionId.toString())
414433
assertThat(updatedContext.viewId).isNotEqualTo(initialViewId)
415-
assertThat(context.viewName).isEqualTo(fakeKey.name)
434+
assertThat(updatedContext.viewName).isEqualTo(fakeKey.name)
416435
assertThat(updatedContext.viewUrl).isEqualTo(fakeUrl)
417-
assertThat(updatedContext.sessionId).isEqualTo(newSessionId.toString())
418-
assertThat(updatedContext.applicationId).isEqualTo(fakeParentContext.applicationId)
436+
assertThat(updatedContext.viewType).isEqualTo(fakeViewType)
437+
assertThat(updatedContext.viewTimestamp)
438+
.isEqualTo(fakeEventTime.timestamp + fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
439+
assertThat(updatedContext.viewTimestampOffset).isEqualTo(fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
440+
assertThat(updatedContext.actionId).isNull()
441+
assertThat(updatedContext.hasReplay).isEqualTo(false)
442+
}
443+
444+
@Test
445+
fun `M update hasReplay W getRumContext() { event processed }`(
446+
forge: Forge
447+
) {
448+
// Given
449+
val fakeEvent = RumRawEvent.StopView(fakeKey, forge.exhaustiveAttributes())
450+
testedScope.handleEvent(fakeEvent, fakeDatadogContext, mockEventWriteScope, mockWriter)
451+
452+
// When
453+
val context = testedScope.getRumContext()
454+
455+
// Then
456+
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
457+
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
458+
assertThat(context.viewId).isEqualTo(testedScope.viewId)
459+
assertThat(context.viewName).isEqualTo(fakeKey.name)
460+
assertThat(context.viewUrl).isEqualTo(fakeUrl)
461+
assertThat(context.viewType).isEqualTo(fakeViewType)
462+
assertThat(context.viewTimestamp)
463+
.isEqualTo(fakeEventTime.timestamp + fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
464+
assertThat(context.viewTimestampOffset).isEqualTo(fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
465+
assertThat(context.actionId).isNull()
466+
assertThat(context.hasReplay).isEqualTo(fakeHasReplay)
467+
}
468+
469+
@Test
470+
fun `M keep hasReplay=true flag if set W getRumContext() { events processed }`(
471+
forge: Forge
472+
) {
473+
// Given
474+
val fakeEvent = forge.anElementFrom(
475+
RumRawEvent.AddLongTask(durationNs = forge.aPositiveLong(), target = forge.aString()),
476+
RumRawEvent.AddError(
477+
message = forge.aString(),
478+
sourceType = forge.getForgery(),
479+
source = forge.getForgery(),
480+
throwable = null,
481+
stacktrace = null,
482+
isFatal = true,
483+
threads = emptyList(),
484+
attributes = forge.exhaustiveAttributes()
485+
),
486+
RumRawEvent.AddCustomTiming(
487+
name = forge.aString()
488+
)
489+
)
490+
val datadogContextWithReplay = fakeDatadogContext.copy(
491+
featuresContext = fakeDatadogContext.featuresContext +
492+
mapOf(Feature.SESSION_REPLAY_FEATURE_NAME to mapOf("has_replay" to fakeHasReplay))
493+
)
494+
whenever(
495+
mockFeaturesContextResolver.resolveViewHasReplay(
496+
datadogContextWithReplay,
497+
testedScope.viewId
498+
)
499+
) doReturn fakeHasReplay
500+
testedScope.handleEvent(fakeEvent, datadogContextWithReplay, mockEventWriteScope, mockWriter)
501+
val fakeStopEvent = RumRawEvent.StopView(fakeKey, forge.exhaustiveAttributes())
502+
val stopViewContext = fakeDatadogContext.copy(
503+
featuresContext = fakeDatadogContext.featuresContext +
504+
mapOf(Feature.SESSION_REPLAY_FEATURE_NAME to mapOf("has_replay" to false))
505+
)
506+
whenever(
507+
mockFeaturesContextResolver.resolveViewHasReplay(
508+
stopViewContext,
509+
testedScope.viewId
510+
)
511+
) doReturn false
512+
testedScope.handleEvent(fakeStopEvent, stopViewContext, mockEventWriteScope, mockWriter)
513+
514+
// When
515+
val context = testedScope.getRumContext()
516+
517+
// Then
518+
assertThat(context.applicationId).isEqualTo(fakeParentContext.applicationId)
519+
assertThat(context.sessionId).isEqualTo(fakeParentContext.sessionId)
520+
assertThat(context.viewId).isEqualTo(testedScope.viewId)
521+
assertThat(context.viewName).isEqualTo(fakeKey.name)
522+
assertThat(context.viewUrl).isEqualTo(fakeUrl)
523+
assertThat(context.viewType).isEqualTo(fakeViewType)
524+
assertThat(context.viewTimestamp)
525+
.isEqualTo(fakeEventTime.timestamp + fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
526+
assertThat(context.viewTimestampOffset).isEqualTo(fakeTimeInfoAtScopeStart.serverTimeOffsetMs)
527+
assertThat(context.actionId).isNull()
528+
assertThat(context.hasReplay).isEqualTo(fakeHasReplay)
419529
}
420530

421531
// endregion
@@ -9741,7 +9851,7 @@ internal class RumViewScopeTest {
97419851
memoryVitalMonitor: VitalMonitor = mockMemoryVitalMonitor,
97429852
frameRateVitalMonitor: VitalMonitor = mockFrameRateVitalMonitor,
97439853
featuresContextResolver: FeaturesContextResolver = mockFeaturesContextResolver,
9744-
type: RumViewType = RumViewType.FOREGROUND,
9854+
type: RumViewType = fakeViewType,
97459855
trackFrustrations: Boolean = fakeTrackFrustrations,
97469856
sampleRate: Float = fakeSampleRate,
97479857
interactionNextViewMetricResolver: InteractionToNextViewMetricResolver =

0 commit comments

Comments
 (0)