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-7435: Add Session Replay startRecordingImmediately ObjC API #2120

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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [FIX] Fix sporadic file overwrite during consent change, ensuring event data integrity. See [#2113][]
- [FIX] Fix trace inconsistency when using `URLSessionInterceptor` or Alamofire extension. See [#2114][]
- [IMPROVEMENT] Add Session Replay `startRecordingImmediately` ObjC API. See [#2120][]

# 2.20.0 / 14-11-2024

Expand Down Expand Up @@ -799,6 +800,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
[#2092]: https://github.com/DataDog/dd-sdk-ios/pull/2092
[#2113]: https://github.com/DataDog/dd-sdk-ios/pull/2113
[#2114]: https://github.com/DataDog/dd-sdk-ios/pull/2114
[#2120]: https://github.com/DataDog/dd-sdk-ios/pull/2120
[@00fa9a]: https://github.com/00FA9A
[@britton-earnin]: https://github.com/Britton-Earnin
[@hengyu]: https://github.com/Hengyu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ - (void)testStartAndStopRecording {
[DDSessionReplay stopRecording];
}

- (void)testStartRecordingImmediately {
DDSessionReplayConfiguration *configuration = [
[DDSessionReplayConfiguration alloc]
initWithReplaySampleRate:100
textAndInputPrivacyLevel:DDTextAndInputPrivacyLevelMaskAll
imagePrivacyLevel:DDImagePrivacyLevelMaskAll
touchPrivacyLevel:DDTouchPrivacyLevelHide
];

configuration.startRecordingImmediately = false;

XCTAssertFalse(configuration.startRecordingImmediately);
}

// MARK: Privacy Overrides
- (void)testSettingAndGettingOverrides {
// Given
Expand Down
12 changes: 10 additions & 2 deletions DatadogSessionReplay/Sources/SessionReplay+objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public final class objc_SessionReplay: NSObject {
@objcMembers
@_spi(objc)
public final class objc_SessionReplayConfiguration: NSObject {
internal var _swift: SessionReplay.Configuration = .init(replaySampleRate: 0)
internal var _swift: SessionReplay.Configuration

/// The sampling rate for Session Replay. It is applied in addition to the RUM session sample rate.
///
Expand Down Expand Up @@ -89,12 +89,20 @@ public final class objc_SessionReplayConfiguration: NSObject {

/// Defines the way user touches (e.g. tap) should be masked.
///
/// Default: `.mask`.
/// Default: `.hide`.
@objc public var touchPrivacyLevel: objc_TouchPrivacyLevel {
set { _swift.touchPrivacyLevel = newValue._swift }
get { .init(_swift.touchPrivacyLevel) }
}

/// Defines it the recording should start automatically. When `true`, the recording starts automatically; when `false` it doesn't, and the recording will need to be started manually.
///
/// Default: `true`.
@objc public var startRecordingImmediately: Bool {
set { _swift.startRecordingImmediately = newValue }
get { _swift.startRecordingImmediately }
}

/// Custom server url for sending replay data.
///
/// Default: `nil`.
Expand Down
8 changes: 8 additions & 0 deletions DatadogSessionReplay/Tests/DDSessionReplayTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class DDSessionReplayTests: XCTestCase {
XCTAssertEqual(config._swift.textAndInputPrivacyLevel, .maskAll)
XCTAssertEqual(config._swift.imagePrivacyLevel, .maskAll)
XCTAssertEqual(config._swift.touchPrivacyLevel, .hide)
XCTAssertTrue(config._swift.startRecordingImmediately)
XCTAssertNil(config._swift.customEndpoint)
}

Expand All @@ -48,6 +49,7 @@ class DDSessionReplayTests: XCTestCase {
XCTAssertEqual(config._swift.textAndInputPrivacyLevel, textAndInputPrivacy._swift)
XCTAssertEqual(config._swift.imagePrivacyLevel, imagePrivacy._swift)
XCTAssertEqual(config._swift.touchPrivacyLevel, touchPrivacy._swift)
XCTAssertTrue(config._swift.startRecordingImmediately)
XCTAssertNil(config._swift.customEndpoint)
}

Expand All @@ -59,6 +61,7 @@ class DDSessionReplayTests: XCTestCase {
let imagePrivacy: objc_ImagePrivacyLevel = [.maskAll, .maskNonBundledOnly, .maskNone].randomElement()!
let touchPrivacy: objc_TouchPrivacyLevel = [.show, .hide].randomElement()!
let url: URL = .mockRandom()
let startRecordingImmediately: Bool = .random()

// When
let config = objc_SessionReplayConfiguration(replaySampleRate: 100)
Expand All @@ -68,6 +71,7 @@ class DDSessionReplayTests: XCTestCase {
config.imagePrivacyLevel = imagePrivacy
config.touchPrivacyLevel = touchPrivacy
config.customEndpoint = url
config.startRecordingImmediately = startRecordingImmediately

// Then
XCTAssertEqual(config._swift.replaySampleRate, sampleRate)
Expand All @@ -76,6 +80,7 @@ class DDSessionReplayTests: XCTestCase {
XCTAssertEqual(config._swift.imagePrivacyLevel, imagePrivacy._swift)
XCTAssertEqual(config._swift.touchPrivacyLevel, touchPrivacy._swift)
XCTAssertEqual(config._swift.customEndpoint, url)
XCTAssertEqual(config._swift.startRecordingImmediately, startRecordingImmediately)
}

func testConfigurationOverridesWithNewApi() {
Expand All @@ -85,6 +90,7 @@ class DDSessionReplayTests: XCTestCase {
let imagePrivacy: objc_ImagePrivacyLevel = [.maskAll, .maskNonBundledOnly, .maskNone].randomElement()!
let touchPrivacy: objc_TouchPrivacyLevel = [.show, .hide].randomElement()!
let url: URL = .mockRandom()
let startRecordingImmediately: Bool = .random()

// When
let config = objc_SessionReplayConfiguration(
Expand All @@ -98,13 +104,15 @@ class DDSessionReplayTests: XCTestCase {
config.imagePrivacyLevel = imagePrivacy
config.touchPrivacyLevel = touchPrivacy
config.customEndpoint = url
config.startRecordingImmediately = startRecordingImmediately

// Then
XCTAssertEqual(config._swift.replaySampleRate, sampleRate)
XCTAssertEqual(config._swift.textAndInputPrivacyLevel, textAndInputPrivacy._swift)
XCTAssertEqual(config._swift.imagePrivacyLevel, imagePrivacy._swift)
XCTAssertEqual(config._swift.touchPrivacyLevel, touchPrivacy._swift)
XCTAssertEqual(config._swift.customEndpoint, url)
XCTAssertEqual(config._swift.startRecordingImmediately, startRecordingImmediately)
}

func testPrivacyLevelsInterop() {
Expand Down
42 changes: 31 additions & 11 deletions api-surface-swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,24 @@ public struct SpanEvent: Encodable
public let applicationVersion: String
public let networkConnectionInfo: NetworkConnectionInfo?
public let mobileCarrierInfo: CarrierInfo?
public struct DeviceInfo: Codable
public enum DeviceType: String, Codable
case mobile = "mobile"
case tablet = "tablet"
case tv = "tv"
case other = "other"
public let brand: String
public let name: String
public let model: String
public let architecture: String
public let type: DeviceType
public let deviceInfo: DeviceInfo
public struct OperatingSystemInfo: Codable
public let name: String
public let version: String
public let build: String?
public let versionMajor: String
public let osInfo: OperatingSystemInfo
public struct UserInfo
public let id: String?
public let name: String?
Expand Down Expand Up @@ -2094,11 +2112,11 @@ public typealias WireframeID = NodeID
public class SessionReplayWireframesBuilder
public struct FontOverride
public init(size: CGFloat?)
public func createShapeWireframe(id: WireframeID,frame: CGRect,clip: SRContentClip? = nil,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createImageWireframe(id: WireframeID,resource: SessionReplayResource,frame: CGRect,mimeType: String = "png",clip: SRContentClip? = nil,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createTextWireframe(id: WireframeID,frame: CGRect,text: String,textFrame: CGRect? = nil,textAlignment: SRTextPosition.Alignment? = nil,clip: SRContentClip? = nil,textColor: CGColor? = nil,font: UIFont? = nil,fontOverride: FontOverride? = nil,fontScalingEnabled: Bool = false,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createPlaceholderWireframe(id: Int64,frame: CGRect,label: String,clip: SRContentClip? = nil) -> SRWireframe
public func visibleWebViewWireframe(id: Int,frame: CGRect,clip: SRContentClip? = nil,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createShapeWireframe(id: WireframeID,frame: CGRect,clip: CGRect,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createImageWireframe(id: WireframeID,resource: SessionReplayResource,frame: CGRect,clip: CGRect,mimeType: String = "png",borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createTextWireframe(id: WireframeID,frame: CGRect,clip: CGRect,text: String,textFrame: CGRect? = nil,textAlignment: SRTextPosition.Alignment? = nil,textColor: CGColor? = nil,font: UIFont? = nil,fontOverride: FontOverride? = nil,fontScalingEnabled: Bool = false,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func createPlaceholderWireframe(id: Int64,frame: CGRect,clip: CGRect,label: String) -> SRWireframe
public func visibleWebViewWireframe(id: Int,frame: CGRect,clip: CGRect,borderColor: CGColor? = nil,borderWidth: CGFloat? = nil,backgroundColor: CGColor? = nil,cornerRadius: CGFloat? = nil,opacity: CGFloat? = nil) -> SRWireframe
public func hiddenWebViewWireframes() -> [SRWireframe]
[?] extension SRContentClip
public static func create(bottom: Int64?,left: Int64?,right: Int64?,top: Int64?) -> SRContentClip
Expand Down Expand Up @@ -2138,12 +2156,13 @@ public protocol SessionReplayResource
func calculateIdentifier() -> String
func calculateData() -> Data
public struct SessionReplayViewAttributes: Equatable
public let frame: CGRect
public let backgroundColor: CGColor?
public let layerBorderColor: CGColor?
public let layerBorderWidth: CGFloat
public let layerCornerRadius: CGFloat
public let alpha: CGFloat
public internal(set) var frame: CGRect
public internal(set) var clip: CGRect
public internal(set) var backgroundColor: CGColor?
public internal(set) var layerBorderColor: CGColor?
public internal(set) var layerBorderWidth: CGFloat
public internal(set) var layerCornerRadius: CGFloat
public internal(set) var alpha: CGFloat
public protocol SessionReplayNodeSemantics
static var importance: Int
var subtreeStrategy: SessionReplayNodeSubtreeStrategy
Expand Down Expand Up @@ -2171,6 +2190,7 @@ public final class objc_SessionReplayConfiguration: NSObject
@objc public var textAndInputPrivacyLevel: objc_TextAndInputPrivacyLevel
@objc public var imagePrivacyLevel: objc_ImagePrivacyLevel
@objc public var touchPrivacyLevel: objc_TouchPrivacyLevel
@objc public var startRecordingImmediately: Bool
@objc public var customEndpoint: URL?
public required init(replaySampleRate: Float,textAndInputPrivacyLevel: objc_TextAndInputPrivacyLevel,imagePrivacyLevel: objc_ImagePrivacyLevel,touchPrivacyLevel: objc_TouchPrivacyLevel)
public required init(replaySampleRate: Float)
Expand Down