Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SDK source value in Session Replay MobileSegment.source property #2293

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
package com.datadog.android.sessionreplay.internal.net

import com.datadog.android.api.InternalLogger
import com.datadog.android.api.context.DatadogContext
import com.datadog.android.sessionreplay.internal.gson.safeGetAsJsonArray
import com.datadog.android.sessionreplay.internal.gson.safeGetAsJsonObject
import com.datadog.android.sessionreplay.internal.gson.safeGetAsLong
import com.datadog.android.sessionreplay.internal.processor.EnrichedRecord
import com.datadog.android.sessionreplay.internal.processor.tryFromSource
import com.datadog.android.sessionreplay.internal.utils.SessionReplayRumContext
import com.datadog.android.sessionreplay.model.MobileSegment
import com.google.gson.JsonArray
Expand All @@ -24,13 +26,16 @@ import com.google.gson.JsonParser
*/
internal class BatchesToSegmentsMapper(private val internalLogger: InternalLogger) {

fun map(batchData: List<ByteArray>): List<Pair<MobileSegment, JsonObject>> {
return groupBatchDataIntoSegments(batchData)
fun map(datadogContext: DatadogContext, batchData: List<ByteArray>): List<Pair<MobileSegment, JsonObject>> {
return groupBatchDataIntoSegments(datadogContext, batchData)
}

// region Internal

private fun groupBatchDataIntoSegments(batchData: List<ByteArray>): List<Pair<MobileSegment, JsonObject>> {
private fun groupBatchDataIntoSegments(
datadogContext: DatadogContext,
batchData: List<ByteArray>
): List<Pair<MobileSegment, JsonObject>> {
return batchData
.asSequence()
.mapNotNull {
Expand Down Expand Up @@ -73,12 +78,13 @@ internal class BatchesToSegmentsMapper(private val internalLogger: InternalLogge
}
.filter { !it.value.isEmpty }
.mapNotNull {
mapToSegment(it.key, it.value)
mapToSegment(datadogContext, it.key, it.value)
}
}

@Suppress("ReturnCount")
private fun mapToSegment(
datadogContext: DatadogContext,
rumContext: SessionReplayRumContext,
records: JsonArray
): Pair<MobileSegment, JsonObject>? {
Expand Down Expand Up @@ -132,7 +138,7 @@ internal class BatchesToSegmentsMapper(private val internalLogger: InternalLogge
// TODO RUM-861 Find a way or alternative to provide a reliable indexInView
indexInView = null,
hasFullSnapshot = hasFullSnapshotRecord,
source = MobileSegment.Source.ANDROID,
source = MobileSegment.Source.tryFromSource(datadogContext.source, internalLogger),
records = emptyList()
)
val segmentAsJsonObject = segment.toJson().safeGetAsJsonObject(internalLogger)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class SegmentRequestFactory(
batchData: List<RawBatchEvent>,
batchMetadata: ByteArray?
): Request {
val serializedSegmentPair = batchToSegmentsMapper.map(batchData.map { it.data })
val serializedSegmentPair = batchToSegmentsMapper.map(context, batchData.map { it.data })
if (serializedSegmentPair.isEmpty()) {
@Suppress("ThrowingInternalException")
throw InvalidPayloadFormatException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.datadog.android.sessionreplay.internal.processor

import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.model.MobileSegment

internal fun MobileSegment.Wireframe.copy(clip: MobileSegment.WireframeClip?): MobileSegment.Wireframe {
Expand All @@ -17,3 +18,23 @@ internal fun MobileSegment.Wireframe.copy(clip: MobileSegment.WireframeClip?): M
is MobileSegment.Wireframe.WebviewWireframe -> this.copy(clip = clip)
}
}

internal fun MobileSegment.Source.Companion.tryFromSource(
source: String,
internalLogger: InternalLogger
): MobileSegment.Source {
return try {
fromJson(source)
} catch (e: NoSuchElementException) {
internalLogger.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ UNKNOWN_MOBILE_SEGMENT_SOURCE_WARNING_MESSAGE_FORMAT.format(java.util.Locale.US, source) },
e
)
MobileSegment.Source.ANDROID
}
}

internal const val UNKNOWN_MOBILE_SEGMENT_SOURCE_WARNING_MESSAGE_FORMAT = "You are using an unknown " +
"source %s for MobileSegment.Source enum."
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package com.datadog.android.sessionreplay.internal.net

import com.datadog.android.api.InternalLogger
import com.datadog.android.api.context.DatadogContext
import com.datadog.android.sessionreplay.forge.ForgeConfigurator
import com.datadog.android.sessionreplay.internal.processor.EnrichedRecord
import com.datadog.android.sessionreplay.internal.utils.SessionReplayRumContext
Expand All @@ -15,6 +16,7 @@ import com.datadog.android.utils.verifyLog
import com.google.gson.JsonParser
import com.google.gson.JsonPrimitive
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.annotation.Forgery
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
import fr.xgouchet.elmyr.junit5.ForgeExtension
import fr.xgouchet.elmyr.jvm.ext.aTimestamp
Expand All @@ -40,11 +42,22 @@ internal class BatchesToSegmentsMapperTest {
@Mock
lateinit var mockInternalLogger: InternalLogger

@Forgery
lateinit var datadogContext: DatadogContext

private lateinit var testedMapper: BatchesToSegmentsMapper

var fakeSegmentSource: MobileSegment.Source? = null

@BeforeEach
fun `set up`() {
fun `set up`(forge: Forge) {
testedMapper = BatchesToSegmentsMapper(mockInternalLogger)

fakeSegmentSource = forge.aNullable { aValueFrom(MobileSegment.Source::class.java) }

datadogContext = datadogContext.copy(
source = fakeSegmentSource?.toJson()?.asString ?: forge.aString()
)
}

@Test
Expand All @@ -71,7 +84,7 @@ internal class BatchesToSegmentsMapperTest {
val fakeBatchData = fakeEnrichedRecords.map { it.toJson().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
assertThat(mappedSegments.size).isEqualTo(fakeEnrichedRecords.size)
Expand Down Expand Up @@ -106,7 +119,7 @@ internal class BatchesToSegmentsMapperTest {
val fakeBatchData = fakeEnrichedRecords.map { it.toJson().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
mappedSegments.forEachIndexed { index, pair ->
Expand Down Expand Up @@ -140,7 +153,7 @@ internal class BatchesToSegmentsMapperTest {
val fakeBatchData = fakeEnrichedRecords.map { it.toJson().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
mappedSegments.forEachIndexed { index, pair ->
Expand Down Expand Up @@ -177,7 +190,7 @@ internal class BatchesToSegmentsMapperTest {
val fakeBatchData = fakeEnrichedRecords.map { it.toJson().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
mappedSegments.forEachIndexed { index, pair ->
Expand All @@ -201,7 +214,7 @@ internal class BatchesToSegmentsMapperTest {
val fakeBatchData = fakeEnrichedRecords.map { it.toJson().toByteArray() }

// When
assertThat(testedMapper.map(fakeBatchData)).isEmpty()
assertThat(testedMapper.map(datadogContext, fakeBatchData)).isEmpty()
}

@Test
Expand All @@ -212,7 +225,7 @@ internal class BatchesToSegmentsMapperTest {
val fakeBatchData = forge.aList { forge.anAlphabeticalString().toByteArray() }

// When
assertThat(testedMapper.map(fakeBatchData)).isEmpty()
assertThat(testedMapper.map(datadogContext, fakeBatchData)).isEmpty()
}

@Test
Expand Down Expand Up @@ -249,7 +262,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
assertThat(testedMapper.map(fakeBatchData)).isEmpty()
assertThat(testedMapper.map(datadogContext, fakeBatchData)).isEmpty()
}

@Test
Expand Down Expand Up @@ -292,7 +305,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)
val expectedRecordsSize = fakeRecords.size - removedRecords
assertThat(mappedSegments.size).isEqualTo(1)
assertThat(mappedSegments[0].first.recordsCount.toInt()).isEqualTo(expectedRecordsSize)
Expand Down Expand Up @@ -329,7 +342,7 @@ internal class BatchesToSegmentsMapperTest {
}
.map { it.toString().toByteArray() }
// When
assertThat(testedMapper.map(fakeBatchData)).isEmpty()
assertThat(testedMapper.map(datadogContext, fakeBatchData)).isEmpty()
mockInternalLogger.verifyLog(
InternalLogger.Level.ERROR,
InternalLogger.Target.TELEMETRY,
Expand Down Expand Up @@ -376,7 +389,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)
assertThat(mappedSegments.size).isEqualTo(1)
val expectedRecordsSize = fakeRecords.size - removedRecords
assertThat(mappedSegments[0].first.recordsCount.toInt()).isEqualTo(expectedRecordsSize)
Expand Down Expand Up @@ -415,7 +428,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
assertThat(testedMapper.map(fakeBatchData)).isEmpty()
assertThat(testedMapper.map(datadogContext, fakeBatchData)).isEmpty()
mockInternalLogger.verifyLog(
InternalLogger.Level.ERROR,
InternalLogger.Target.TELEMETRY,
Expand Down Expand Up @@ -462,7 +475,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
val expectedRecordsSize = fakeRecords.size - removedRecords
Expand Down Expand Up @@ -502,7 +515,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// Then
assertThat(testedMapper.map(fakeBatchData)).isEmpty()
assertThat(testedMapper.map(datadogContext, fakeBatchData)).isEmpty()
mockInternalLogger.verifyLog(
InternalLogger.Level.ERROR,
InternalLogger.Target.TELEMETRY,
Expand Down Expand Up @@ -549,7 +562,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
val expectedRecordsSize = fakeRecords.size - removedRecords
Expand Down Expand Up @@ -603,7 +616,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
val expectedRecordsSize = fakeRecords.size - removedRecords
Expand Down Expand Up @@ -651,7 +664,7 @@ internal class BatchesToSegmentsMapperTest {
.map { it.toString().toByteArray() }

// When
val mappedSegments = testedMapper.map(fakeBatchData)
val mappedSegments = testedMapper.map(datadogContext, fakeBatchData)

// Then
val expectedRecordsSize = fakeRecords.size - removedRecords
Expand All @@ -673,7 +686,7 @@ internal class BatchesToSegmentsMapperTest {
recordsCount = records.size.toLong(),
indexInView = null,
hasFullSnapshot = hasFullSnapshot(),
source = MobileSegment.Source.ANDROID,
source = fakeSegmentSource ?: MobileSegment.Source.ANDROID,
records = records
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal class SegmentRequestFactoryTest {
fakeBatchMetadata = forge.aNullable { forge.aString().toByteArray() }
whenever(mockSegmentRequestBodyFactory.create(fakeDataGroup))
.thenReturn(mockRequestBody)
whenever(mockBatchesToSegmentsMapper.map(fakeBatchData.map { it.data }))
whenever(mockBatchesToSegmentsMapper.map(fakeDatadogContext, fakeBatchData.map { it.data }))
.thenReturn(fakeDataGroup)
testedRequestFactory = SegmentRequestFactory(
customEndpointUrl = null,
Expand Down Expand Up @@ -160,7 +160,7 @@ internal class SegmentRequestFactoryTest {
@Test
fun `M throw exception W create(){ payload is broken }`() {
// Given
whenever(mockBatchesToSegmentsMapper.map(fakeBatchData.map { it.data }))
whenever(mockBatchesToSegmentsMapper.map(fakeDatadogContext, fakeBatchData.map { it.data }))
.thenReturn(emptyList())

// When
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.datadog.android.sessionreplay.internal.processor

import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.model.MobileSegment
import fr.xgouchet.elmyr.Forge
import fr.xgouchet.elmyr.junit5.ForgeExtension
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.Extensions
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.isA
import org.mockito.kotlin.isNull
import org.mockito.kotlin.verify
import java.util.Locale

@Extensions(
ExtendWith(ForgeExtension::class),
ExtendWith(MockitoExtension::class)
)
internal class MobileSegmentExtTest {

@Mock
lateinit var mockInternalLogger: InternalLogger

// region MobileSegment.Source

@Test
fun `M resolve the MobileSegment source W tryFromSource`(
forge: Forge
) {
// Given
val fakeValidSource = forge.aValueFrom(MobileSegment.Source::class.java)

// When
val source = MobileSegment.Source.tryFromSource(fakeValidSource.toJson().asString, mockInternalLogger)

// Then
assertThat(source).isEqualTo(fakeValidSource)
}

@Test
fun `M return default value W tryFromSource { unknown source }`(
forge: Forge
) {
// Given
val fakeInvalidSource = forge.aString()

// When
val source = MobileSegment.Source.tryFromSource(fakeInvalidSource, mockInternalLogger)

// Then
assertThat(source).isEqualTo(MobileSegment.Source.ANDROID)
}

@Test
fun `M send an error maintainer log W tryFromSource { unknown source }`(
forge: Forge
) {
// Given
val fakeInvalidSource = forge.aString()

// When
MobileSegment.Source.tryFromSource(fakeInvalidSource, mockInternalLogger)

// Then
argumentCaptor<() -> String>() {
verify(mockInternalLogger).log(
level = eq(InternalLogger.Level.ERROR),
target = eq(InternalLogger.Target.MAINTAINER),
messageBuilder = capture(),
throwable = isA<NoSuchElementException>(),
onlyOnce = eq(false),
additionalProperties = isNull()
)

assertThat(firstValue()).isEqualTo(
UNKNOWN_MOBILE_SEGMENT_SOURCE_WARNING_MESSAGE_FORMAT.format(
Locale.US,
fakeInvalidSource
)
)
}
}

// endregion
}