RUMM-2873 Allow users to stop a RUM session#1356
Conversation
Still need to add tests that ensure that the RUM Context is updated on the core at the proper times and add integration tests.
0xnm
left a comment
There was a problem hiding this comment.
I've added few comments, most of them are quite minor and not related to the functional part of the change, but some are for clarifying the functional part.
| app:layout_constraintHorizontal_bias="0.498" | ||
| app:layout_constraintStart_toStartOf="parent" | ||
| app:layout_constraintTop_toBottomOf="@+id/end_session" /> | ||
|
|
||
| <Button | ||
| android:id="@+id/end_session" | ||
| android:layout_width="wrap_content" | ||
| android:layout_height="wrap_content" | ||
| android:layout_marginTop="140dp" | ||
| android:text="@string/end_session" | ||
| app:layout_constraintEnd_toEndOf="parent" | ||
| app:layout_constraintHorizontal_bias="0.5" |
There was a problem hiding this comment.
minor: probably we don't need ConstraintLayout with bias 0.5/0.498 (and 0.498/0.501 for another layout). Good old LinearLayout with gravity should work.
There was a problem hiding this comment.
Just used to doing it from iOS at this point 🙂
| lateinit var endSessionButton: Button | ||
| lateinit var startKioskButton: Button |
There was a problem hiding this comment.
no need to have properties, can be just variables inside method.
| session = ViewEvent.ViewEventSession( | ||
| id = rumContext.sessionId, | ||
| type = ViewEvent.ViewEventSessionType.USER, | ||
| hasReplay = hasReplay | ||
| hasReplay = hasReplay, | ||
| isActive = rumContext.isSessionActive |
There was a problem hiding this comment.
I believe we are hitting the problem here by relying on the rumContext. Imagine the following situation: session was stopped, but view needs to wait for the child events to complete. But before they complete, another session was started, meaning by the time this view will receive terminal view update, it will be another session in the RumContext (active=true with another session ID), different from the one which view was attributed to originally.
I think the only solution in this case is to take session ID at the time view is constructed and rely on it, instead of relying on the shared RumContext.
There was a problem hiding this comment.
I'll double check, but I think we're good here because this isn't the shared global RUM context. It's created by going up / down the scope tree, so a new session starting will be in a different tree than this.
| internal data class RumContext( | ||
| val applicationId: String = NULL_UUID, | ||
| val sessionId: String = NULL_UUID, | ||
| val isSessionActive: Boolean = true, |
There was a problem hiding this comment.
I believe it should be false by default, to have a consistency with default sessionId which is NULL_UUID, meaning there is no session.
|
|
||
| // Then | ||
| val newSession = testedScope.activeSession | ||
| check(newSession is RumSessionScope) |
There was a problem hiding this comment.
I guess check is used instead of assertThat(...).isInstanceOf(...) to benefit from smart case below?
There was a problem hiding this comment.
Yes. I was following other examples but that seems to be the reason it was done there so I kept it 🙂
| sessionIsActive = false | ||
| ) | ||
| ) | ||
|
|
||
| onView(withId(R.id.end_session)).perform(click()) |
There was a problem hiding this comment.
why do we expect sessionIsActive = false before we click on End Session? shouldn't onView(withId(R.id.end_session)).perform(click()) be above?
There was a problem hiding this comment.
This is a weird thing about how these tests are written. I need to capture current scope before I actually end the session, so I have to create the expected event before I click the button. The same reason the other tests add the expected "stop view" before actually stopping the view.
Co-authored-by: Nikita Ogorodnikov <[email protected]>
ff44dfb to
6fafbb3
Compare
Add more tests checking for session state.
6fafbb3 to
c6414ac
Compare
Also add stopSession to untestable APIs
This is used over pulling the RUMViewScope during session stop.
Codecov Report
@@ Coverage Diff @@
## develop #1356 +/- ##
===========================================
+ Coverage 81.87% 81.95% +0.08%
===========================================
Files 362 363 +1
Lines 12903 13014 +111
Branches 2164 2187 +23
===========================================
+ Hits 10564 10665 +101
- Misses 1679 1688 +9
- Partials 660 661 +1
|
0xnm
left a comment
There was a problem hiding this comment.
lgtm! I've left some comments, but they are mostly not related to the main functionality.
| applicationDisplayed | ||
| ) | ||
|
|
||
| internal val lastActiveViewScope: RumViewScope? |
| childScopes.add(newSession) | ||
| if (event !is RumRawEvent.StartView) { | ||
| lastActiveViewInfo?.let { | ||
| it.keyRef.get()?.let { key -> |
There was a problem hiding this comment.
should we log the case when key is missing? just to simplify debugging on the customer side.
There was a problem hiding this comment.
Added a message. Can you review that it makes sense?
| ) | ||
|
|
||
| // Then | ||
| assertThat(testedScope.childScopes.count()).isEqualTo(1) |
There was a problem hiding this comment.
it will give more meaningful error
| assertThat(testedScope.childScopes.count()).isEqualTo(1) | |
| assertThat(testedScope.childScopes).hasSize(1) |
| } | ||
|
|
||
| @Test | ||
| fun `M create a new session W handleEvent { no active sessions, user interaction } `( |
There was a problem hiding this comment.
it seems that name of the test is a bit misleading, here we send the view event and not an action, so I guess it should not be user interaction in the name?
788c379 to
57edb19
Compare
0xnm
left a comment
There was a problem hiding this comment.
lgtm! I've added a minor comment to help debugging.
|
|
||
| val activeSession: RumScope? | ||
| get() { | ||
| return childScopes.find { it.isActive() } |
There was a problem hiding this comment.
just a question out of my head: find will return first element matching the criteria. Normally we shouldn't expect childScopes to contain multiple isActive sessions, but for the debugging do you think it will be useful to log when this invariant (only one active session) is broken? wdyt?
There was a problem hiding this comment.
Yes, I actually have a similar telemetry in iOS. Maybe not here, though as it could get noisy. I'll see if I can find a better place to add it.
| childScopes.add(newSession) | ||
| if (event !is RumRawEvent.StartView) { | ||
| lastActiveViewInfo?.let { | ||
| it.keyRef.get()?.let { key -> |
| internalLogger.log( | ||
| InternalLogger.Level.WARN, | ||
| InternalLogger.Target.USER, | ||
| LAST_ACTIVE_VIEW_GONE_WARNING_MESSAGE.format(it.name) |
There was a problem hiding this comment.
minor: better to explicitly specify the Locale
| LAST_ACTIVE_VIEW_GONE_WARNING_MESSAGE.format(it.name) | |
| LAST_ACTIVE_VIEW_GONE_WARNING_MESSAGE.format(Locale.US, it.name) |
57edb19 to
61192e1
Compare
|
@0xnm I added telemetry for checking if we create multiple active sessions during session start. I think that should handle the case you're talking about. |
61192e1 to
c08ad2f
Compare
What does this PR do?
This adds a method to manually end a RUM session with stopRumSession. This immediately stops the active view and sets the current session to inactive. A new session starts automatically when a user action is sent (startView or addUserAction).
RUM events now have a new property: session.isActive. This can be null, and is only currently set in RUMViewEvents. The state of this member is pulled from the parent session context.
I've modified RumApplicationScope to support holding multiple RumSessionScopes in a similar way to how RumViewManager can handle multiple Views until all of their events have been processed.
When a RUMStopSession event is received, the Application Scope forwards it to the Session, which marks itself as inactive, then forwards the event to the all views, which mark themselves as inactive and send a ViewUpdate event which captures the session now being inactive.
Provided views have finished processing all of their user actions and resources, the session is removed from available sessions on the ApplicationScope. Otherwise, it sicks around (marked as inactive) until all events are complete.
Review checklist (to be filled by reviewers)