Skip to content

Commit

Permalink
Merge pull request #2384 from DataDog/jmoskovich/rum-7150/support-mul…
Browse files Browse the repository at this point in the history
…tiple-extensions

RUM-7150: Add multiple extension support
  • Loading branch information
jonathanmos authored Nov 14, 2024
2 parents 1ff6204 + 6e09dbf commit 4d7cd9b
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 38 deletions.
4 changes: 4 additions & 0 deletions detekt_custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ datadog:
- "kotlin.collections.List.contains(kotlin.String)"
- "kotlin.collections.List.contains(kotlin.String)"
- "kotlin.collections.List.count()"
- "kotlin.collections.List.distinct()"
- "kotlin.collections.List.drop(kotlin.Int)"
- "kotlin.collections.List.elementAtOrNull(kotlin.Int)"
- "kotlin.collections.List.filter(kotlin.Function1)"
Expand Down Expand Up @@ -1014,17 +1015,20 @@ datadog:
- "kotlin.collections.MutableMap?.forEach(kotlin.Function1)"
- "kotlin.collections.MutableSet.add(com.datadog.android.api.feature.FeatureContextUpdateReceiver?)"
- "kotlin.collections.MutableSet.add(com.datadog.android.core.internal.persistence.ConsentAwareStorage.Batch)"
- "kotlin.collections.MutableSet.add(com.datadog.android.sessionreplay.ExtensionSupport)"
- "kotlin.collections.MutableSet.add(com.datadog.android.telemetry.internal.TelemetryEventId)"
- "kotlin.collections.MutableSet.add(java.io.File)"
- "kotlin.collections.MutableSet.add(kotlin.String)"
- "kotlin.collections.MutableSet.add(kotlin.String?)"
- "kotlin.collections.MutableSet.addAll(kotlin.collections.Collection)"
- "kotlin.collections.MutableSet.any(kotlin.Function1)"
- "kotlin.collections.MutableSet.clear()"
- "kotlin.collections.MutableSet.contains(com.datadog.android.telemetry.internal.TelemetryEventId)"
- "kotlin.collections.MutableSet.contains(kotlin.String)"
- "kotlin.collections.MutableSet.contains(kotlin.String?)"
- "kotlin.collections.MutableSet.filter(kotlin.Function1)"
- "kotlin.collections.MutableSet.firstOrNull(kotlin.Function1)"
- "kotlin.collections.MutableSet.flatMap(kotlin.Function1)"
- "kotlin.collections.MutableSet.forEach(kotlin.Function1)"
- "kotlin.collections.MutableSet.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)"
- "kotlin.collections.MutableSet.map(kotlin.Function1)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ class com.datadog.android.sessionreplay.compose.ComposeExtensionSupport : com.da
override fun getCustomViewMappers(): List<com.datadog.android.sessionreplay.MapperTypeWrapper<*>>
override fun getOptionSelectorDetectors(): List<com.datadog.android.sessionreplay.recorder.OptionSelectorDetector>
override fun getCustomDrawableMapper(): List<com.datadog.android.sessionreplay.utils.DrawableToColorMapper>
override fun name(): String
annotation com.datadog.android.sessionreplay.compose.ExperimentalSessionReplayApi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public final class com/datadog/android/sessionreplay/compose/ComposeExtensionSup
public fun getCustomDrawableMapper ()Ljava/util/List;
public fun getCustomViewMappers ()Ljava/util/List;
public fun getOptionSelectorDetectors ()Ljava/util/List;
public fun name ()Ljava/lang/String;
}

public abstract interface annotation class com/datadog/android/sessionreplay/compose/ExperimentalSessionReplayApi : java/lang/annotation/Annotation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ class ComposeExtensionSupport : ExtensionSupport {
override fun getCustomDrawableMapper(): List<DrawableToColorMapper> {
return emptyList()
}

override fun name(): String =
COMPOSE_EXTENSION_SUPPORT_NAME

internal companion object {
internal const val COMPOSE_EXTENSION_SUPPORT_NAME = "ComposeExtensionSupport"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ class ComposeExtensionSupportTest {
val composeMapper = customMappers.firstOrNull { it.supportsView(mockView) }?.getUnsafeMapper()
assertThat(composeMapper).isInstanceOf(SemanticsWireframeMapper::class.java)
}

@Test
fun `M return name W name()`() {
// When
val name = testedExtensionSupport.name()

// Then
assertThat(name).isEqualTo(ComposeExtensionSupport.COMPOSE_EXTENSION_SUPPORT_NAME)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ class com.datadog.android.sessionreplay.material.MaterialExtensionSupport : com.
override fun getCustomViewMappers(): List<com.datadog.android.sessionreplay.MapperTypeWrapper<*>>
override fun getOptionSelectorDetectors(): List<com.datadog.android.sessionreplay.recorder.OptionSelectorDetector>
override fun getCustomDrawableMapper(): List<com.datadog.android.sessionreplay.utils.DrawableToColorMapper>
override fun name(): String
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ public final class com/datadog/android/sessionreplay/material/MaterialExtensionS
public fun getCustomDrawableMapper ()Ljava/util/List;
public fun getCustomViewMappers ()Ljava/util/List;
public fun getOptionSelectorDetectors ()Ljava/util/List;
public fun name ()Ljava/lang/String;
}

Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@ class MaterialExtensionSupport : ExtensionSupport {
override fun getCustomDrawableMapper(): List<DrawableToColorMapper> {
return listOf(materialDrawableToColorMapper)
}

override fun name(): String =
MATERIAL_EXTENSION_SUPPORT_NAME

internal companion object {
internal const val MATERIAL_EXTENSION_SUPPORT_NAME = "MaterialExtensionSupport"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.datadog.android.sessionreplay.material

import com.datadog.android.sessionreplay.material.MaterialExtensionSupport.Companion.MATERIAL_EXTENSION_SUPPORT_NAME
import com.datadog.android.sessionreplay.material.internal.MaterialOptionSelectorDetector
import com.datadog.android.sessionreplay.material.internal.SliderWireframeMapper
import com.datadog.android.sessionreplay.material.internal.TabWireframeMapper
Expand All @@ -25,7 +26,7 @@ import org.mockito.quality.Strictness
@MockitoSettings(strictness = Strictness.LENIENT)
class MaterialExtensionSupportTest {

lateinit var testedMaterialExtensionSupport: MaterialExtensionSupport
private lateinit var testedMaterialExtensionSupport: MaterialExtensionSupport

@BeforeEach
fun `set up`() {
Expand Down Expand Up @@ -67,4 +68,10 @@ class MaterialExtensionSupportTest {
assertThat(customDetectors.size).isEqualTo(1)
assertThat(customDetectors[0]).isInstanceOf(MaterialOptionSelectorDetector::class.java)
}

@Test
fun `M return name of extension W name`() {
// Then
assertThat(testedMaterialExtensionSupport.name()).isEqualTo(MATERIAL_EXTENSION_SUPPORT_NAME)
}
}
4 changes: 4 additions & 0 deletions features/dd-sdk-android-session-replay/api/apiSurface
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
interface com.datadog.android.sessionreplay.ExtensionSupport
fun name(): String
fun getCustomViewMappers(): List<MapperTypeWrapper<*>>
fun getOptionSelectorDetectors(): List<com.datadog.android.sessionreplay.recorder.OptionSelectorDetector>
fun getCustomDrawableMapper(): List<com.datadog.android.sessionreplay.utils.DrawableToColorMapper>
Expand All @@ -10,6 +11,8 @@ data class com.datadog.android.sessionreplay.MapperTypeWrapper<T: android.view.V
constructor(Class<T>, com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper<T>)
fun supportsView(android.view.View): Boolean
fun getUnsafeMapper(): com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper<android.view.View>
override fun equals(Any?): Boolean
override fun hashCode(): Int
interface com.datadog.android.sessionreplay.PrivacyLevel
fun android.view.View.setSessionReplayHidden(Boolean)
fun android.view.View.setSessionReplayImagePrivacy(ImagePrivacy?)
Expand All @@ -21,6 +24,7 @@ object com.datadog.android.sessionreplay.SessionReplay
fun stopRecording(com.datadog.android.api.SdkCore = Datadog.getInstance())
data class com.datadog.android.sessionreplay.SessionReplayConfiguration
class Builder
constructor()
constructor(Float = SAMPLE_IN_ALL_SESSIONS)
fun addExtensionSupport(ExtensionSupport): Builder
fun useCustomEndpoint(String): Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ public abstract interface class com/datadog/android/sessionreplay/ExtensionSuppo
public abstract fun getCustomDrawableMapper ()Ljava/util/List;
public abstract fun getCustomViewMappers ()Ljava/util/List;
public abstract fun getOptionSelectorDetectors ()Ljava/util/List;
public abstract fun name ()Ljava/lang/String;
}

public final class com/datadog/android/sessionreplay/ImagePrivacy : java/lang/Enum, com/datadog/android/sessionreplay/PrivacyLevel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
*/
interface ExtensionSupport {

/**
* Identifier for the extension.
* @return the name of this extension.
*/
fun name(): String

/**
* Use this method if you want to apply a custom [WireframeMapper] for a specific [View].
* @return the list of [MapperTypeWrapper]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,17 @@ data class MapperTypeWrapper<T : View>(
fun getUnsafeMapper(): WireframeMapper<View> {
return mapper as WireframeMapper<View>
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as MapperTypeWrapper<*>

return type == other.type
}

override fun hashCode(): Int {
return type.hashCode()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
package com.datadog.android.sessionreplay

import androidx.annotation.FloatRange
import com.datadog.android.sessionreplay.internal.NoOpExtensionSupport
import com.datadog.android.api.InternalLogger
import com.datadog.android.sessionreplay.recorder.OptionSelectorDetector
import com.datadog.android.sessionreplay.utils.DrawableToColorMapper
import java.util.Locale

/**
* Describes configuration to be used for the Session Replay feature.
Expand All @@ -31,11 +32,34 @@ data class SessionReplayConfiguration internal constructor(

/**
* A Builder class for a [SessionReplayConfiguration].
* @param sampleRate must be a value between 0 and 100. A value of 0
* means no session will be recorded, 100 means all sessions will be recorded.
* If this value is not provided then Session Replay will default to a 100 sample rate.
*/
class Builder(@FloatRange(from = 0.0, to = 100.0) private val sampleRate: Float = SAMPLE_IN_ALL_SESSIONS) {
@Suppress("TooManyFunctions")
class Builder {
private val logger: InternalLogger
private val sampleRate: Float

/**
* Calling this constructor will default to a 100% session sampling rate.
*/
constructor() : this(SAMPLE_IN_ALL_SESSIONS, InternalLogger.UNBOUND)

/**
* @param sampleRate must be a value between 0 and 100. A value of 0
* means no session will be recorded, 100 means all sessions will be recorded.
* If this value is not provided then Session Replay will default to a 100 sample rate.
*/
constructor(
@FloatRange(from = 0.0, to = 100.0) sampleRate: Float = SAMPLE_IN_ALL_SESSIONS
) : this(sampleRate, InternalLogger.UNBOUND)

internal constructor(
@FloatRange(from = 0.0, to = 100.0) sampleRate: Float,
logger: InternalLogger
) {
this.sampleRate = sampleRate
this.logger = logger
}

private var customEndpointUrl: String? = null
private var privacy = SessionReplayPrivacy.MASK

Expand All @@ -46,7 +70,7 @@ data class SessionReplayConfiguration internal constructor(
private var startRecordingImmediately = true
private var touchPrivacy = TouchPrivacy.HIDE
private var textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL
private var extensionSupport: ExtensionSupport = NoOpExtensionSupport()
private var extensionSupportSet: MutableSet<ExtensionSupport> = mutableSetOf()
private var dynamicOptimizationEnabled = true
private var systemRequirementsConfiguration = SystemRequirementsConfiguration.NONE

Expand All @@ -57,7 +81,16 @@ data class SessionReplayConfiguration internal constructor(
* @see [ExtensionSupport.getLegacyCustomViewMappers]
*/
fun addExtensionSupport(extensionSupport: ExtensionSupport): Builder {
this.extensionSupport = extensionSupport
if (this.extensionSupportSet.any { it.name() == extensionSupport.name() }) {
logger.log(
target = InternalLogger.Target.MAINTAINER,
level = InternalLogger.Level.WARN,
messageBuilder = { DUPLICATE_EXTENSION_DETECTED.format(Locale.US, extensionSupport.name()) }
)
} else {
this.extensionSupportSet.add(extensionSupport)
}

return this
}

Expand Down Expand Up @@ -188,8 +221,8 @@ data class SessionReplayConfiguration internal constructor(
touchPrivacy = touchPrivacy,
textAndInputPrivacy = textAndInputPrivacy,
customMappers = customMappers(),
customOptionSelectorDetectors = extensionSupport.getOptionSelectorDetectors(),
customDrawableMappers = extensionSupport.getCustomDrawableMapper(),
customOptionSelectorDetectors = optionsSelectorDetectors(),
customDrawableMappers = customDrawableMappers(),
sampleRate = sampleRate,
startRecordingImmediately = startRecordingImmediately,
dynamicOptimizationEnabled = dynamicOptimizationEnabled,
Expand All @@ -198,11 +231,32 @@ data class SessionReplayConfiguration internal constructor(
}

private fun customMappers(): List<MapperTypeWrapper<*>> {
return extensionSupport.getCustomViewMappers()
val allItems = extensionSupportSet.flatMap { it.getCustomViewMappers() }

allItems.groupBy { it }
.filter { it.value.size > 1 }
.forEach { (item, _) ->
logger.log(
target = InternalLogger.Target.MAINTAINER,
level = InternalLogger.Level.WARN,
messageBuilder = { DUPLICATE_MAPPER_DETECTED.format(Locale.US, item.type) }
)
}

return allItems.distinct().toList()
}

private fun customDrawableMappers(): List<DrawableToColorMapper> =
extensionSupportSet.flatMap { it.getCustomDrawableMapper() }.toList()

private fun optionsSelectorDetectors(): List<OptionSelectorDetector> =
extensionSupportSet.flatMap { it.getOptionSelectorDetectors() }.toList()

internal companion object {
internal const val SAMPLE_IN_ALL_SESSIONS = 100.0f
internal const val DUPLICATE_EXTENSION_DETECTED =
"Attempting to add support twice for the same extension %s. The duplicate will be ignored."
internal const val DUPLICATE_MAPPER_DETECTED = "Duplicate mapper for %s. The duplicate will be ignored."
}
}
}

This file was deleted.

Loading

0 comments on commit 4d7cd9b

Please sign in to comment.