Skip to content

[RUM-8652] Allow definition of custom implementations of specific SR methods#2516

Merged
cdn34dd merged 1 commit into
developfrom
carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods
Mar 7, 2025
Merged

[RUM-8652] Allow definition of custom implementations of specific SR methods#2516
cdn34dd merged 1 commit into
developfrom
carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods

Conversation

@cdn34dd

@cdn34dd cdn34dd commented Feb 27, 2025

Copy link
Copy Markdown
Contributor

What does this PR do?

  • Exposes a new SessionReplayConfigutation option to allow clients to pass their own implementations of specific methods used in the Session Replay SDK.

  • Updates the SDK code to use the overriden function if provided and falling back to the original implementation otherwise.
    ( Currently only getCurrentActivity is allowed to be redefined, but there's a high likelihood that more will be needed )

Motivation

What inspired you to submit this pull request?

Certain parts of the session replay SDK don't work as intended when ran in a react-native environment, leading to broken replays. In order to keep platform specific code out of the android SDK, the goal is to allow each client to add their own implementations of the parts that don't work.

Additional Notes

We keep the setInternalCallback only accessible through a internal proxy in order to not expose internal config options. In practice it would look something like this:

        val internalCallback = ReactNativeSessionReplayInternalCallback(reactContext)
        
        val configuration = SessionReplayConfiguration.Builder(replaySampleRate.toFloat())
            /* .... */
            .setImagePrivacy(/* .... */)
            .setTouchPrivacy(/* .... */)
            .setTextAndInputPrivacy(/* .... */)
            .let { builder ->
                _SessionReplayInternalProxy(builder).setInternalCallback(internalCallback)
            }

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)

Comment on lines +11 to +13
data class SessionReplayCustomCallbacks(
val getCurrentWindows: (() -> List<Window>)? = null
) No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Why did you chose to make it a data class instead of an interface?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general idea was to make a simple configuration object we could add to the configuration builder that holds optional callbacks, allowing clients to set only the ones they needed. But here both would work.

@cdn34dd
cdn34dd marked this pull request as ready for review March 3, 2025 07:12
@cdn34dd
cdn34dd requested review from a team as code owners March 3, 2025 07:12
@cdn34dd
cdn34dd force-pushed the carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods branch from 8d1bb14 to fdb1b74 Compare March 3, 2025 08:00
@0xnm

0xnm commented Mar 3, 2025

Copy link
Copy Markdown
Member

I think we shouldn't go this way (by exposing it in the public API along with other methods), it should be an internal API accessible from the proxy.

*/
@InternalApi
@Suppress(
"UndocumentedPublicClass",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class has been documented above, why do we still need this Suppression here?

import android.view.Window

data class SessionReplayCustomCallbacks(
val getCurrentWindows: (() -> List<Window>)? = null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of having a data class with only one callback inside, you can just make it as an interface

@NoOpImplementation
interface SessionReplayCustomCallback{
      fun getCurrentWindows() : List<Window>
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure how many callbacks there will be for sure, for now it's just one, if it turns out to be just this one, we will likely not even need this and I'll change this then.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having several callbacks feature, is that possible to have only one callback but with more functions in it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what that means, can you explain it a bit more or give an example ? Just for context, all these functions we need to override from RN or other project can all be unrelated to each other and can affect different parts of the sdk.

@ambushwork ambushwork Mar 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, what you intent to do should be like:

data class SessionReplayCustomCallbacks(
    val getCurrentWindows: (() -> List<Window>)? = null
    val doSomethingElseA: ((SomeInputA) -> SomeOutputA)? = null
    val doSomethingElseB: ((SomeInputB) -> List<SomeOutputB>)? = null
)

Instead of doing that, you can do :

@NoOpImplementation
interface SessionReplayCustomCallback{

     fun getCurrentWindows() : List<Window>

     fun doSomethingElseA(input: SomeInputA) : SomeOutputA

     fun doSomethingElseA(input: SomeInputB) : SomeOutputB
}

In that case you will have only one callback but can be called in different placesto serve for your need.

private var extensionSupportSet: MutableSet<ExtensionSupport> = mutableSetOf()
private var dynamicOptimizationEnabled = true
private var systemRequirementsConfiguration = SystemRequirementsConfiguration.NONE
private var customCallbacks: SessionReplayCustomCallbacks = SessionReplayCustomCallbacks()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it expected that the class name and field name are all pluriel?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say in this case yes, but what would you suggest ?

@ambushwork ambushwork Mar 3, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually we keep class name singular to represent a single instance of an object, unless the class itself presents a pluriel collection such as data class Users(val users: List<User>), and for the field name it should be like:

private var customCallback : SessionReplayCustomCallback = SessionReplayCustomCallback()

// or 

private var customCallbacks : List<SessionReplayCustomCallback> = SessionReplayCustomCallback()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind changing the name, but having something called SessionReplayCustomCallback, will be incorrect as soon as another callback is added no ?

@0xnm

0xnm commented Mar 3, 2025

Copy link
Copy Markdown
Member

Please ignore my comment above, I had an old version of this PR opened. I will review a new one.

0xnm
0xnm previously approved these changes Mar 3, 2025

@0xnm 0xnm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left few suggestions, but nothing blocking. LGTM.

import android.view.Window

data class SessionReplayCustomCallbacks(
val getCurrentWindows: (() -> List<Window>)? = null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: there is no need to use get prefix for val property, it can be just val currentWindows

Comment on lines +172 to +175
logger = internalLogger,
resourceResolver = resourceResolver,
logger = internalLogger, resourceResolver = resourceResolver,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be reverted

Comment on lines +11 to +12
data class SessionReplayCustomCallbacks(
val getCurrentWindows: (() -> List<Window>)? = null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe some explanation/docs here will be useful

"ClassNaming",
"VariableNaming"
)
class _SessionReplayInternalProxy {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_SessionReplayInternalProxy.setCustomCallbacks(builder, customCallbacks)

_SessionReplayInternalProxy(builder).setCustomCallbacks(customCallbacks)

@cdn34dd
cdn34dd force-pushed the carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods branch 4 times, most recently from cf978c3 to 8e98c06 Compare March 5, 2025 10:45
@codecov-commenter

codecov-commenter commented Mar 5, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 83.87097% with 5 lines in your changes missing coverage. Please review.

Project coverage is 70.07%. Comparing base (a9007ed) to head (f0da63c).
Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
...nreplay/internal/recorder/SessionReplayRecorder.kt 40.00% 2 Missing and 1 partial ⚠️
...nreplay/internal/SessionReplayLifecycleCallback.kt 75.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2516      +/-   ##
===========================================
- Coverage    70.07%   70.07%   -0.01%     
===========================================
  Files          796      797       +1     
  Lines        29970    29990      +20     
  Branches      5016     5018       +2     
===========================================
+ Hits         21001    21013      +12     
- Misses        7574     7576       +2     
- Partials      1395     1401       +6     
Files with missing lines Coverage Δ
...com/datadog/android/sessionreplay/SessionReplay.kt 72.73% <100.00%> (+0.63%) ⬆️
...ndroid/sessionreplay/SessionReplayConfiguration.kt 96.77% <100.00%> (+0.18%) ⬆️
...droid/sessionreplay/_SessionReplayInternalProxy.kt 100.00% <100.00%> (ø)
.../sessionreplay/internal/DefaultRecorderProvider.kt 93.80% <100.00%> (+0.10%) ⬆️
...oid/sessionreplay/internal/SessionReplayFeature.kt 98.75% <100.00%> (+0.01%) ⬆️
...nreplay/internal/SessionReplayLifecycleCallback.kt 62.50% <75.00%> (-4.17%) ⬇️
...nreplay/internal/recorder/SessionReplayRecorder.kt 95.10% <40.00%> (-2.00%) ⬇️

... and 27 files with indirect coverage changes

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cdn34dd
cdn34dd requested a review from ambushwork March 5, 2025 15:30
ambushwork
ambushwork previously approved these changes Mar 6, 2025
xgouchet
xgouchet previously approved these changes Mar 6, 2025
@cdn34dd
cdn34dd dismissed stale reviews from xgouchet and ambushwork via 5cfbd1c March 6, 2025 17:09
@cdn34dd
cdn34dd force-pushed the carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods branch from 8e98c06 to 5cfbd1c Compare March 6, 2025 17:09
@cdn34dd
cdn34dd requested a review from ambushwork March 6, 2025 17:12
… builder

- have the option to set `getCurrentActivity` in order to allow
fragments lifecycle callbacks to be registered after
Application.onCreate phase
- make `setInternalCallback`to be available only through internal proxy

tmp
@cdn34dd
cdn34dd force-pushed the carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods branch from 5cfbd1c to f0da63c Compare March 6, 2025 17:47

private val currentActiveWindows = WeakHashMap<Window, Any?>()

fun setCurrentWindow(activity: Activity) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the function name is setCurrentWindow but you are taking activity as the argument, which makes a bit confusing,
you can just change to setCurrentWindow(window: Window) and let the call site worry about how to retrieve the window.

@cdn34dd
cdn34dd merged commit 5a98bd8 into develop Mar 7, 2025
@cdn34dd
cdn34dd deleted the carlosnogueira/RUM-8652/add-config-option-to-override-specific-sr-methods branch March 7, 2025 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants