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

RUM-7150: Add multiple extension support #2384

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
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