Skip to content

Commit

Permalink
RUM-7150: Add multiple extension support
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanmos committed Nov 13, 2024
1 parent 11c0d06 commit 2cbb9c1
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 35 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)
}
}
3 changes: 3 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 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 Expand Up @@ -58,8 +59,10 @@ public final class com/datadog/android/sessionreplay/SessionReplayConfiguration$
public synthetic fun <init> (FILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addExtensionSupport (Lcom/datadog/android/sessionreplay/ExtensionSupport;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
public final fun build ()Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;
public final fun getLogger ()Lcom/datadog/android/api/InternalLogger;
public final fun setDynamicOptimizationEnabled (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
public final fun setImagePrivacy (Lcom/datadog/android/sessionreplay/ImagePrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
public final fun setLogger (Lcom/datadog/android/api/InternalLogger;)V
public final fun setPrivacy (Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
public final fun setSystemRequirements (Lcom/datadog/android/sessionreplay/SystemRequirementsConfiguration;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
public final fun setTextAndInputPrivacy (Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder;
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 @@ -35,7 +36,18 @@ data class SessionReplayConfiguration internal constructor(
* 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(
@FloatRange(from = 0.0, to = 100.0) private val sampleRate: Float = SAMPLE_IN_ALL_SESSIONS
) {
private var logger = InternalLogger.UNBOUND
internal constructor(
@FloatRange(from = 0.0, to = 100.0) sampleRate: Float = SAMPLE_IN_ALL_SESSIONS,
logger: InternalLogger
) : this(sampleRate) {
this.logger = logger
}

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

Expand All @@ -46,7 +58,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 +69,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 +209,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 +219,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 2cbb9c1

Please sign in to comment.