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

RUMM-1265 Correctly handle RUM events when app in background #504

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
20 changes: 19 additions & 1 deletion Sources/Datadog/RUM/RUMMonitor/Scopes/RUMSessionScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider {
switch command {
case let command as RUMStartViewCommand:
startView(on: command)
case is RUMStartResourceCommand, is RUMAddUserActionCommand, is RUMStartUserActionCommand:
handleOrphanStartCommand(command: command)
default:
break
}
Expand Down Expand Up @@ -132,7 +134,23 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider {
)
}

// MARK: - Private
// MARK: - Private
private func handleOrphanStartCommand(command: RUMCommand) {
if viewScopes.isEmpty {
viewScopes.append(
RUMViewScope(
parent: self,
dependencies: dependencies,
identity: RUMViewScope.Constants.backgroundViewURL,
path: RUMViewScope.Constants.backgroundViewURL,
name: RUMViewScope.Constants.backgroundViewName,
attributes: command.attributes,
customTimings: [:],
startTime: command.time
)
)
}
}

private func timedOutOrExpired(currentTime: Date) -> Bool {
let timeElapsedSinceLastInteraction = currentTime.timeIntervalSince(lastInteractionTime)
Expand Down
8 changes: 6 additions & 2 deletions Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import Foundation

internal class RUMViewScope: RUMScope, RUMContextProvider {
struct Constants {
static let backgroundViewURL = "com/datadog/background/view"
static let backgroundViewName = "Background"
}

// MARK: - Child Scopes

/// Active Resource scopes, keyed by .resourceKey.
Expand All @@ -33,7 +38,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider {
/// The name of this View, used as the `VIEW NAME` in RUM Explorer.
let viewName: String
/// The start time of this View.
private let viewStartTime: Date
let viewStartTime: Date
/// Date correction to server time.
private let dateCorrection: DateCorrection
/// Tells if this View is the active one.
Expand Down Expand Up @@ -129,7 +134,6 @@ internal class RUMViewScope: RUMScope, RUMContextProvider {
case let command as RUMStopViewCommand where identity.equals(command.identity):
isActiveView = false
needsViewUpdate = true

case let command as RUMAddViewTimingCommand where isActiveView:
customTimings[command.timingName] = command.time.timeIntervalSince(viewStartTime).toInt64Nanoseconds
needsViewUpdate = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import XCTest
@testable import Datadog

class RUMSessionScopeTests: XCTestCase {
// MARK: - Unit Tests

func testDefaultContext() {
let parent: RUMApplicationScope = .mockWith(rumApplicationID: "rum-123")
let scope = RUMSessionScope(parent: parent, dependencies: .mockAny(), samplingRate: 100, startTime: .mockAny())
Expand Down Expand Up @@ -77,6 +79,66 @@ class RUMSessionScopeTests: XCTestCase {
XCTAssertEqual(scope.viewScopes.count, 0)
}

func testWhenNoViewScope_andReceivedStartResourceCommand_itCreatesNewViewScope() {
let parent = RUMContextProviderMock()
let currentTime = Date()

let scope = RUMSessionScope(parent: parent, dependencies: .mockAny(), samplingRate: 100, startTime: Date())

_ = scope.process(command: RUMStartResourceCommand.mockWith(resourceKey: "/resource/1", time: currentTime))

XCTAssertEqual(scope.viewScopes.count,1)
XCTAssertEqual(scope.viewScopes[0].viewStartTime, currentTime)
XCTAssertEqual(scope.viewScopes[0].viewName, RUMViewScope.Constants.backgroundViewName)
XCTAssertEqual(scope.viewScopes[0].viewPath, RUMViewScope.Constants.backgroundViewURL)
}

func testWhenNoViewScope_andReceivedStartActionCommand_itCreatesNewViewScope() {
let parent = RUMContextProviderMock()
let currentTime = Date()

let scope = RUMSessionScope(parent: parent, dependencies: .mockAny(), samplingRate: 100, startTime: Date())

_ = scope.process(command: RUMStartUserActionCommand.mockWith(time: currentTime))

XCTAssertEqual(scope.viewScopes.count,1)
XCTAssertEqual(scope.viewScopes[0].viewStartTime, currentTime)
XCTAssertEqual(scope.viewScopes[0].viewName, RUMViewScope.Constants.backgroundViewName)
XCTAssertEqual(scope.viewScopes[0].viewPath, RUMViewScope.Constants.backgroundViewURL)
}

func testWhenNoViewScope_andReceivedAddUserActionCommand_itCreatesNewViewScope() {
let parent = RUMContextProviderMock()
let currentTime = Date()

let scope = RUMSessionScope(parent: parent, dependencies: .mockAny(), samplingRate: 100, startTime: Date())

_ = scope.process(command: RUMAddUserActionCommand.mockWith(time: currentTime))

XCTAssertEqual(scope.viewScopes.count,1)
XCTAssertEqual(scope.viewScopes[0].viewStartTime, currentTime)
XCTAssertEqual(scope.viewScopes[0].viewName, RUMViewScope.Constants.backgroundViewName)
XCTAssertEqual(scope.viewScopes[0].viewPath, RUMViewScope.Constants.backgroundViewURL)
}

func testWhenActiveViewScope_andReceivingStartCommand_itDoesNotCreateNewViewScope() {
let parent = RUMContextProviderMock()
let currentTime = Date()

let scope = RUMSessionScope(parent: parent, dependencies: .mockAny(), samplingRate: 100, startTime: Date())
_ = scope.process(command: generateRandomNotValidStartCommand())
_ = scope.process(command: RUMAddUserActionCommand.mockWith(time: currentTime))
Copy link
Member

Choose a reason for hiding this comment

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

I think we want to call generateRandomValidStartCommand() here, no?

XCTAssertEqual(scope.viewScopes.count, 1)
}

func testWhenNoActiveViewScope_andReceivingNotValidStartCommand_itDoesNotCreateNewViewScope() {
let parent = RUMContextProviderMock()

let scope = RUMSessionScope(parent: parent, dependencies: .mockAny(), samplingRate: 100, startTime: Date())
_ = scope.process(command: generateRandomNotValidStartCommand())
XCTAssertEqual(scope.viewScopes.count, 0)
}

func testWhenSessionIsSampled_itDoesNotCreateViewScopes() {
let parent = RUMContextProviderMock()

Expand All @@ -88,4 +150,14 @@ class RUMSessionScopeTests: XCTestCase {
)
XCTAssertEqual(scope.viewScopes.count, 0)
}

// MARK: - Private

private func generateRandomValidStartCommand() -> RUMCommand {
return [RUMStartUserActionCommand.mockAny(), RUMStartResourceCommand.mockAny(), RUMAddUserActionCommand.mockAny()].randomElement()!
}

private func generateRandomNotValidStartCommand() -> RUMCommand {
return [RUMStopViewCommand.mockAny(), RUMStopResourceCommand.mockAny(), RUMStopUserActionCommand.mockAny(), RUMAddCurrentViewErrorCommand.mockWithErrorObject()].randomElement()!
}
}