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

Allow users to manually end a RUM session #1219

Merged
merged 7 commits into from
Mar 27, 2023

Conversation

fuzzybinary
Copy link
Contributor

What and why?

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

How?

First, may 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.

Second, I've modified RUMApplicationScope to support holding multiple RUMSessionScopes in a similar way to how Sessions 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

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference
  • Add CHANGELOG entry for user facing changes

Custom CI job configuration (optional)

  • Run unit tests
  • Run integration tests
  • Run smoke tests

This includes most of the work for the SessionScope for stopping a session, but does not include any ApplicationScope work for restarting sessions or ViewScope work for sending session status to intake.
Adds `is_active` to Session information. Default it to true for the time being.
This requires ApplicationScope keep multiple sessions, and discard them as they're completed.
@fuzzybinary fuzzybinary requested a review from a team as a code owner March 21, 2023 21:21
@fuzzybinary fuzzybinary force-pushed the jward/RUMM-2872-manually-end-session branch from b594e2d to 93db921 Compare March 22, 2023 14:44

if command is RUMStopSessionCommand {
// Reach in and grab the last active view
lastActiveView = activeSession?.viewScopes.first(where: { $0.isActiveView } )
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm interested in thoughts on this approach. This feels like it's pulling in some extra dependencies in the ViewScope, but I couldn't find a good way to make activating the previous SessionScope's view work, even with the existing refresh logic.

@fuzzybinary fuzzybinary force-pushed the jward/RUMM-2872-manually-end-session branch from 4a04e61 to e64a3e8 Compare March 22, 2023 19:41
@fuzzybinary fuzzybinary force-pushed the jward/RUMM-2872-manually-end-session branch from e64a3e8 to 076b87a Compare March 22, 2023 20:54
Copy link
Contributor

@maciejburda maciejburda left a comment

Choose a reason for hiding this comment

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

Great Effort!

Left a couple of minor suggestions 🙌

rumMonitor.stopView(viewController: self)
}

@IBAction func didTapDownloadResourceButton(_ sender: Any) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm not mistaken you can change the sender type to UIButton and avoid force cast

Comment on lines 73 to 74
if scope.isActive {
// False, but still active means we timed out or expired, refresh the session
Copy link
Contributor

Choose a reason for hiding this comment

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

From the comment it feels like isSessionActive should be checked here as well?

I don't see this param used elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm confused by this? Do you mean applicationActive?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't get what False is referring to here, since we are inside of isActive, which is true

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, It's the return value of scope.process If it returned true return the scope, False, we check isActive. I'll change the comment to make it clearer.

return nil
})

// Sanity telemety, only end up with one active session
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! 💪

@@ -135,51 +158,63 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider {
}

if !isSampled {
return true // discard all events in this session
// Make sure sessions end even if they are sampled
Copy link
Contributor

Choose a reason for hiding this comment

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

nice catch!

Comment on lines 633 to 634
self.applicationScope.sessionScope?.context ??
self.applicationScope.context
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this odd formatting is more common for ??, but if linter is not complaining I think it's fine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TBH I think the linter is what changed it.


guard context.sessionID != .nullUUID else {
// if Session was sampled or not yet started
Copy link
Contributor

Choose a reason for hiding this comment

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

Is that not true anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had some issues with this function I may have removed the comment by accident. I'll put it back.

…wift

Co-authored-by: Maciej Burda <maciej.burda@datadoghq.com>
Comment on lines 30 to 33
var time: Date
var attributes: [AttributeKey: AttributeValue] = [:]
var canStartBackgroundView = false // no, stopping a session should not start a backgorund session
var isUserInteraction = false
Copy link
Member

Choose a reason for hiding this comment

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

/nit if we are not mutating any of those, they can be let 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They're var getters in the RUMCommand protocol. Is it possible to change them to let in the implementation?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, if they are get only, they can be `let 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 169 to 203
var deactivating = false
if isActive {
if command is RUMStopSessionCommand {
isActive = false
deactivating = true
} else if let startApplicationCommand = command as? RUMApplicationStartCommand {
startApplicationLaunchView(on: startApplicationCommand, context: context, writer: writer)
} else if let startViewCommand = command as? RUMStartViewCommand {
// Start view scope explicitly on receiving "start view" command
startView(on: startViewCommand, context: context)
} else if !hasActiveView {
// Otherwise, if there is no active view scope, consider starting artificial scope for handling this command
let handlingRule = RUMOffViewEventsHandlingRule(
sessionState: state,
isAppInForeground: context.applicationStateHistory.currentSnapshot.state.isRunningInForeground,
isBETEnabled: backgroundEventTrackingEnabled
)

switch handlingRule {
case .handleInBackgroundView where command.canStartBackgroundView:
startBackgroundView(on: command, context: context)
default:
if !(command is RUMKeepSessionAliveCommand) { // it is expected to receive 'keep alive' while no active view (when tracking WebView events)
// As no view scope will handle this command, warn the user on dropping it.
DD.logger.warn(
switch handlingRule {
case .handleInBackgroundView where command.canStartBackgroundView:
startBackgroundView(on: command, context: context)
default:
if !(command is RUMKeepSessionAliveCommand) { // it is expected to receive 'keep alive' while no active view (when tracking WebView events)
// As no view scope will handle this command, warn the user on dropping it.
DD.logger.warn(
"""
\(String(describing: command)) was detected, but no view is active. To track views automatically, try calling the
DatadogConfiguration.Builder.trackUIKitRUMViews() method. You can also track views manually using
the RumMonitor.startView() and RumMonitor.stopView() methods.
"""
)
)
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

The conditions and state management are difficult to follow and error prone IMO. It might be helpful to split this into multiple methods, would that be possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved the "no active view" portion to its own function. Let me know if that helps or it you think it needs more.

Copy link
Member

Choose a reason for hiding this comment

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

Perfect, thank you!

@fuzzybinary fuzzybinary force-pushed the jward/RUMM-2872-manually-end-session branch from 0155739 to 05a8278 Compare March 27, 2023 15:03
Copy link
Member

@maxep maxep left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

@fuzzybinary fuzzybinary merged commit 5a657d8 into develop Mar 27, 2023
@fuzzybinary fuzzybinary deleted the jward/RUMM-2872-manually-end-session branch March 27, 2023 16:56
@ncreated ncreated mentioned this pull request Mar 31, 2023
6 tasks
@maxep maxep mentioned this pull request Apr 3, 2023
6 tasks
@maciejburda maciejburda mentioned this pull request Apr 19, 2023
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.

3 participants