From 2f0ee2d4b8d208f256b55ce5c14f7f81785dd264 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 27 May 2024 18:36:17 +0200 Subject: [PATCH 01/71] [Session replay] Support bg color + hidden nav bar --- .../NodeRecorders/UINavigationBarRecorder.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift index b13908200a..ed3985220e 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift @@ -15,6 +15,10 @@ internal struct UINavigationBarRecorder: NodeRecorder { return nil } + guard attributes.isVisible else { + return InvisibleElement.constant + } + let builder = UINavigationBarWireframesBuilder( wireframeRect: inferOccupiedFrame(of: navigationBar, in: context), wireframeID: context.ids.nodeID(view: navigationBar, nodeRecorder: self), @@ -38,6 +42,11 @@ internal struct UINavigationBarRecorder: NodeRecorder { private func inferColor(of navigationBar: UINavigationBar) -> CGColor { // TODO: RUMM-2791 Enhance appearance of `UITabBar` and `UINavigationBar` in SR + + if let color = navigationBar.backgroundColor { + return color.cgColor + } + if #available(iOS 13.0, *) { switch UITraitCollection.current.userInterfaceStyle { case .light: From 72095649879195faf9e5c68bd0a9cde0bc017813 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Fri, 31 May 2024 10:56:18 +0200 Subject: [PATCH 02/71] Add app hang tracking to Objective-C API --- .../DatadogObjc/DDRUMConfigurationTests.swift | 7 +++ .../DatadogObjc/ObjcAPITests/DDRUM+apiTests.m | 4 ++ DatadogObjc/Sources/RUM/RUM+objc.swift | 5 ++ api-surface-objc | 41 ++++++++++++- api-surface-swift | 58 +++++++++++++++++-- 5 files changed, 108 insertions(+), 7 deletions(-) diff --git a/DatadogCore/Tests/DatadogObjc/DDRUMConfigurationTests.swift b/DatadogCore/Tests/DatadogObjc/DDRUMConfigurationTests.swift index 2ad6fd04b8..9cb9201af7 100644 --- a/DatadogCore/Tests/DatadogObjc/DDRUMConfigurationTests.swift +++ b/DatadogCore/Tests/DatadogObjc/DDRUMConfigurationTests.swift @@ -106,6 +106,13 @@ class DDRUMConfigurationTests: XCTestCase { XCTAssertEqual(swift.longTaskThreshold, random) } + func testAppHangThreshold() { + let random: TimeInterval = .mockRandom() + objc.appHangThreshold = random + XCTAssertEqual(objc.appHangThreshold, random) + XCTAssertEqual(swift.appHangThreshold, random) + } + func testVitalsUpdateFrequency() { objc.vitalsUpdateFrequency = .frequent XCTAssertEqual(swift.vitalsUpdateFrequency, .frequent) diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m index 4b144cc68c..fc9a00fa33 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUM+apiTests.m @@ -99,6 +99,10 @@ - (void)testDDRUMConfigurationAPI { config.longTaskThreshold = 1; XCTAssertEqual(config.longTaskThreshold, 1); + XCTAssertEqual(config.appHangThreshold, 0); + config.appHangThreshold = 1; + XCTAssertEqual(config.appHangThreshold, 1); + XCTAssertEqual(config.vitalsUpdateFrequency, DDRUMVitalsFrequencyAverage); config.vitalsUpdateFrequency = DDRUMVitalsFrequencyFrequent; XCTAssertEqual(config.vitalsUpdateFrequency, DDRUMVitalsFrequencyFrequent); diff --git a/DatadogObjc/Sources/RUM/RUM+objc.swift b/DatadogObjc/Sources/RUM/RUM+objc.swift index f86218a3d9..aec38aceb7 100644 --- a/DatadogObjc/Sources/RUM/RUM+objc.swift +++ b/DatadogObjc/Sources/RUM/RUM+objc.swift @@ -371,6 +371,11 @@ public class DDRUMConfiguration: NSObject { get { swiftConfig.longTaskThreshold ?? 0 } } + @objc public var appHangThreshold: TimeInterval { + set { swiftConfig.appHangThreshold = newValue } + get { swiftConfig.appHangThreshold ?? 0 } + } + @objc public var vitalsUpdateFrequency: DDRUMVitalsFrequency { set { swiftConfig.vitalsUpdateFrequency = newValue.swiftType } get { DDRUMVitalsFrequency(swiftType: swiftConfig.vitalsUpdateFrequency) } diff --git a/api-surface-objc b/api-surface-objc index 055e43734f..941e1e5609 100644 --- a/api-surface-objc +++ b/api-surface-objc @@ -231,6 +231,7 @@ public class DDRUMConfiguration: NSObject @objc public var trackFrustrations: Bool @objc public var trackBackgroundEvents: Bool @objc public var longTaskThreshold: TimeInterval + @objc public var appHangThreshold: TimeInterval @objc public var vitalsUpdateFrequency: DDRUMVitalsFrequency public func setViewEventMapper(_ mapper: @escaping (DDRUMViewEvent) -> DDRUMViewEvent) public func setResourceEventMapper(_ mapper: @escaping (DDRUMResourceEvent) -> DDRUMResourceEvent?) @@ -243,6 +244,8 @@ public class DDRUM: NSObject public static func enable(with configuration: DDRUMConfiguration) public class DDRUMMonitor: NSObject public static func shared() -> DDRUMMonitor + public func currentSessionID(completion: @escaping (String?) -> Void) + public func stopSession() public func startView(viewController: UIViewController,name: String?,attributes: [String: Any]) public func stopView(viewController: UIViewController,attributes: [String: Any]) public func startView(key: String,name: String?,attributes: [String: Any]) @@ -263,6 +266,8 @@ public class DDRUMMonitor: NSObject public func addAction(type: DDRUMActionType,name: String,attributes: [String: Any]) public func addAttribute(forKey key: String,value: Any) public func removeAttribute(forKey key: String) + public func addFeatureFlagEvaluation(name: String, value: Any) + @objc public var debug: Bool public class DDRUMActionEvent: NSObject @objc public var dd: DDRUMActionEventDD @objc public var action: DDRUMActionEventAction @@ -585,6 +590,7 @@ public class DDRUMErrorEventError: NSObject @objc public var binaryImages: [DDRUMErrorEventErrorBinaryImages]? @objc public var category: DDRUMErrorEventErrorCategory @objc public var causes: [DDRUMErrorEventErrorCauses]? + @objc public var csp: DDRUMErrorEventErrorCSP? @objc public var fingerprint: String? @objc public var handling: DDRUMErrorEventErrorHandling @objc public var handlingStack: String? @@ -597,6 +603,7 @@ public class DDRUMErrorEventError: NSObject @objc public var sourceType: DDRUMErrorEventErrorSourceType @objc public var stack: String? @objc public var threads: [DDRUMErrorEventErrorThreads]? + @objc public var timeSinceAppStart: NSNumber? @objc public var type: String? @objc public var wasTruncated: NSNumber? public class DDRUMErrorEventErrorBinaryImages: NSObject @@ -625,6 +632,12 @@ public enum DDRUMErrorEventErrorCausesSource: Int case webview case custom case report +public class DDRUMErrorEventErrorCSP: NSObject + @objc public var disposition: DDRUMErrorEventErrorCSPDisposition +public enum DDRUMErrorEventErrorCSPDisposition: Int + case none + case enforce + case report public enum DDRUMErrorEventErrorHandling: Int case none case handled @@ -1028,18 +1041,22 @@ public class DDRUMResourceEventRUMOperatingSystem: NSObject @objc public var versionMajor: String public class DDRUMResourceEventResource: NSObject @objc public var connect: DDRUMResourceEventResourceConnect? + @objc public var decodedBodySize: NSNumber? @objc public var dns: DDRUMResourceEventResourceDNS? @objc public var download: DDRUMResourceEventResourceDownload? @objc public var duration: NSNumber? + @objc public var encodedBodySize: NSNumber? @objc public var firstByte: DDRUMResourceEventResourceFirstByte? @objc public var graphql: DDRUMResourceEventResourceGraphql? @objc public var id: String? @objc public var method: DDRUMResourceEventResourceRUMMethod @objc public var provider: DDRUMResourceEventResourceProvider? @objc public var redirect: DDRUMResourceEventResourceRedirect? + @objc public var renderBlockingStatus: DDRUMResourceEventResourceRenderBlockingStatus @objc public var size: NSNumber? @objc public var ssl: DDRUMResourceEventResourceSSL? @objc public var statusCode: NSNumber? + @objc public var transferSize: NSNumber? @objc public var type: DDRUMResourceEventResourceResourceType @objc public var url: String public class DDRUMResourceEventResourceConnect: NSObject @@ -1097,6 +1114,10 @@ public enum DDRUMResourceEventResourceProviderProviderType: Int public class DDRUMResourceEventResourceRedirect: NSObject @objc public var duration: NSNumber @objc public var start: NSNumber +public enum DDRUMResourceEventResourceRenderBlockingStatus: Int + case none + case blocking + case nonBlocking public class DDRUMResourceEventResourceSSL: NSObject @objc public var duration: NSNumber @objc public var start: NSNumber @@ -1675,6 +1696,7 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject @objc public var batchProcessingLevel: NSNumber? @objc public var batchSize: NSNumber? @objc public var batchUploadFrequency: NSNumber? + @objc public var compressIntakeRequests: NSNumber? @objc public var dartVersion: String? @objc public var defaultPrivacyLevel: String? @objc public var forwardConsoleLogs: DDTelemetryConfigurationEventTelemetryConfigurationForwardConsoleLogs? @@ -1694,7 +1716,11 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject @objc public var storeContextsAcrossPages: NSNumber? @objc public var telemetryConfigurationSampleRate: NSNumber? @objc public var telemetrySampleRate: NSNumber? + @objc public var telemetryUsageSampleRate: NSNumber? + @objc public var traceContextInjection: DDTelemetryConfigurationEventTelemetryConfigurationTraceContextInjection @objc public var traceSampleRate: NSNumber? + @objc public var tracerApi: String? + @objc public var tracerApiVersion: String? @objc public var trackBackgroundEvents: NSNumber? @objc public var trackCrossPlatformLongTasks: NSNumber? @objc public var trackErrors: NSNumber? @@ -1710,6 +1736,7 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject @objc public var trackSessionAcrossSubdomains: NSNumber? @objc public var trackUserInteractions: NSNumber? @objc public var trackViewsManually: NSNumber? + @objc public var trackingConsent: String? @objc public var unityVersion: String? @objc public var useAllowedTracingOrigins: NSNumber? @objc public var useAllowedTracingUrls: NSNumber? @@ -1719,6 +1746,7 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject @objc public var useFirstPartyHosts: NSNumber? @objc public var useLocalEncryption: NSNumber? @objc public var usePartitionedCrossSiteSessionCookie: NSNumber? + @objc public var usePciIntake: NSNumber? @objc public var useProxy: NSNumber? @objc public var useSecureSessionCookie: NSNumber? @objc public var useTracing: NSNumber? @@ -1736,6 +1764,10 @@ public enum DDTelemetryConfigurationEventTelemetryConfigurationSelectedTracingPr case b3 case b3multi case tracecontext +public enum DDTelemetryConfigurationEventTelemetryConfigurationTraceContextInjection: Int + case none + case all + case sampled public enum DDTelemetryConfigurationEventTelemetryConfigurationViewTrackingStrategy: Int case none case activityViewTrackingStrategy @@ -1763,12 +1795,15 @@ public class DDB3HTTPHeadersWriter: NSObject @objc public var traceHeaderFields: [String: String] public convenience init(samplingRate: Float,injectEncoding: DDInjectEncoding = .single) public init(sampleRate: Float = 20,injectEncoding: DDInjectEncoding = .single) - public init(samplingStrategy: DDTraceSamplingStrategy,injectEncoding: DDInjectEncoding = .single) + public init(samplingStrategy: DDTraceSamplingStrategy,injectEncoding: DDInjectEncoding = .single,traceContextInjection: DDTraceContextInjection = .all) public class DDHTTPHeadersWriter: NSObject @objc public var traceHeaderFields: [String: String] public convenience init(samplingRate: Float) public init(sampleRate: Float = 20) - public init(samplingStrategy: DDTraceSamplingStrategy) + public init(samplingStrategy: DDTraceSamplingStrategy,traceContextInjection: DDTraceContextInjection) +public enum DDTraceContextInjection: Int + case all + case sampled public class DDTraceSamplingStrategy: NSObject public static func headBased() -> DDTraceSamplingStrategy public static func custom(sampleRate: Float) -> DDTraceSamplingStrategy @@ -1776,7 +1811,7 @@ public class DDW3CHTTPHeadersWriter: NSObject @objc public var traceHeaderFields: [String: String] public convenience init(samplingRate: Float) public init(sampleRate: Float = 20) - public init(samplingStrategy: DDTraceSamplingStrategy) + public init(samplingStrategy: DDTraceSamplingStrategy,traceContextInjection: DDTraceContextInjection) public class DDTraceConfiguration: NSObject override public init() @objc public var sampleRate: Float diff --git a/api-surface-swift b/api-surface-swift index 33f2c8afa7..cfe14c5475 100644 --- a/api-surface-swift +++ b/api-surface-swift @@ -75,11 +75,19 @@ public struct LogEvent: Encodable public let email: String? public var extraInfo: [String: Encodable] public struct Error + public struct BinaryImage: Codable + public let arch: String? + public let isSystem: Bool + public let loadAddress: String? + public let maxAddress: String? + public let name: String + public let uuid: String public var kind: String? public var message: String? public var stack: String? public var sourceType: String = "ios" public var fingerprint: String? + public var binaryImages: [BinaryImage]? public struct DeviceInfo: Codable public let brand: String public let name: String @@ -190,6 +198,11 @@ public protocol LogEventMapper case warn case error case critical +[?] extension OpenTelemetryApi.TraceState + public func w3c() -> String +public class OTelTracerProvider: OpenTelemetryApi.TracerProvider + public init(in core: DatadogCoreProtocol = CoreRegistry.default) + public func get(instrumentationName: String, instrumentationVersion: String?) -> OpenTelemetryApi.Tracer public struct OTTags public static let component = "component" public static let dbInstance = "db.instance" @@ -295,12 +308,14 @@ public enum Trace public struct URLSessionTracking public var firstPartyHostsTracing: FirstPartyHostsTracing public enum FirstPartyHostsTracing - case trace(hosts: Set, sampleRate: Float = 20) - case traceWithHeaders(hostsWithHeaders: [String: Set], sampleRate: Float = 20) + case trace(hosts: Set,sampleRate: Float = 20,traceControlInjection: TraceContextInjection = .all) + case traceWithHeaders(hostsWithHeaders: [String: Set],sampleRate: Float = 20,traceControlInjection: TraceContextInjection = .all) public init(firstPartyHostsTracing: FirstPartyHostsTracing) public init(sampleRate: Float = 100,service: String? = nil,tags: [String: Encodable]? = nil,urlSessionTracking: URLSessionTracking? = nil,bundleWithRumEnabled: Bool = true,networkInfoEnabled: Bool = false,eventMapper: EventMapper? = nil,customEndpoint: URL? = nil) public enum SpanTags public static let resource = "resource.name" + public static let operation = "operation.name" + public static let service = "service.name" public class Tracer public static func shared(in core: DatadogCoreProtocol = CoreRegistry.default) -> OTTracer @@ -495,6 +510,7 @@ public struct RUMErrorEvent: RUMDataModel public let binaryImages: [BinaryImages]? public let category: Category? public var causes: [Causes]? + public let csp: CSP? public var fingerprint: String? public let handling: Handling? public let handlingStack: String? @@ -507,6 +523,7 @@ public struct RUMErrorEvent: RUMDataModel public let sourceType: SourceType? public var stack: String? public let threads: [Threads]? + public let timeSinceAppStart: Int64? public let type: String? public let wasTruncated: Bool? public struct BinaryImages: Codable @@ -534,6 +551,11 @@ public struct RUMErrorEvent: RUMDataModel case webview = "webview" case custom = "custom" case report = "report" + public struct CSP: Codable + public let disposition: Disposition? + public enum Disposition: String, Codable + case enforce = "enforce" + case report = "report" public enum Handling: String, Codable case handled = "handled" case unhandled = "unhandled" @@ -764,18 +786,22 @@ public struct RUMResourceEvent: RUMDataModel public let width: Double public struct Resource: Codable public let connect: Connect? + public let decodedBodySize: Int64? public let dns: DNS? public let download: Download? public let duration: Int64? + public let encodedBodySize: Int64? public let firstByte: FirstByte? public var graphql: Graphql? public let id: String? public let method: RUMMethod? public let provider: Provider? public let redirect: Redirect? + public let renderBlockingStatus: RenderBlockingStatus? public let size: Int64? public let ssl: SSL? public let statusCode: Int64? + public let transferSize: Int64? public let type: ResourceType public var url: String public struct Connect: Codable @@ -821,6 +847,9 @@ public struct RUMResourceEvent: RUMDataModel public struct Redirect: Codable public let duration: Int64 public let start: Int64 + public enum RenderBlockingStatus: String, Codable + case blocking = "blocking" + case nonBlocking = "non-blocking" public struct SSL: Codable public let duration: Int64 public let start: Int64 @@ -1238,6 +1267,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel public let batchProcessingLevel: Int64? public let batchSize: Int64? public let batchUploadFrequency: Int64? + public let compressIntakeRequests: Bool? public var dartVersion: String? public var defaultPrivacyLevel: String? public let forwardConsoleLogs: ForwardConsoleLogs? @@ -1257,7 +1287,11 @@ public struct TelemetryConfigurationEvent: RUMDataModel public let storeContextsAcrossPages: Bool? public let telemetryConfigurationSampleRate: Int64? public let telemetrySampleRate: Int64? + public let telemetryUsageSampleRate: Int64? + public var traceContextInjection: TraceContextInjection? public let traceSampleRate: Int64? + public var tracerApi: String? + public var tracerApiVersion: String? public var trackBackgroundEvents: Bool? public var trackCrossPlatformLongTasks: Bool? public var trackErrors: Bool? @@ -1273,6 +1307,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel public let trackSessionAcrossSubdomains: Bool? public var trackUserInteractions: Bool? public var trackViewsManually: Bool? + public let trackingConsent: String? public var unityVersion: String? public let useAllowedTracingOrigins: Bool? public let useAllowedTracingUrls: Bool? @@ -1282,6 +1317,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel public var useFirstPartyHosts: Bool? public let useLocalEncryption: Bool? public let usePartitionedCrossSiteSessionCookie: Bool? + public var usePciIntake: Bool? public var useProxy: Bool? public let useSecureSessionCookie: Bool? public let useTracing: Bool? @@ -1302,6 +1338,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel case b3 = "b3" case b3multi = "b3multi" case tracecontext = "tracecontext" + public enum TraceContextInjection: String, Codable + case all = "all" + case sampled = "sampled" public enum ViewTrackingStrategy: String, Codable case activityViewTrackingStrategy = "ActivityViewTrackingStrategy" case fragmentViewTrackingStrategy = "FragmentViewTrackingStrategy" @@ -1476,8 +1515,8 @@ public enum RUM case rare [?] extension RUM.Configuration.URLSessionTracking public enum FirstPartyHostsTracing - case trace(hosts: Set, sampleRate: Float = 20) - case traceWithHeaders(hostsWithHeaders: [String: Set], sampleRate: Float = 20) + case trace(hosts: Set,sampleRate: Float = 20,traceControlInjection: TraceContextInjection = .all) + case traceWithHeaders(hostsWithHeaders: [String: Set],sampleRate: Float = 20,traceControlInjection: TraceContextInjection = .all) public init(firstPartyHostsTracing: RUM.Configuration.URLSessionTracking.FirstPartyHostsTracing? = nil,resourceAttributesProvider: RUM.ResourceAttributesProvider? = nil) [?] extension RUM.Configuration public init(applicationID: String,sessionSampleRate: Float = 100,uiKitViewsPredicate: UIKitRUMViewsPredicate? = nil,uiKitActionsPredicate: UIKitRUMActionsPredicate? = nil,urlSessionTracking: URLSessionTracking? = nil,trackFrustrations: Bool = true,trackBackgroundEvents: Bool = false,longTaskThreshold: TimeInterval? = 0.1,appHangThreshold: TimeInterval? = nil,vitalsUpdateFrequency: VitalsFrequency? = .average,viewEventMapper: RUM.ViewEventMapper? = nil,resourceEventMapper: RUM.ResourceEventMapper? = nil,actionEventMapper: RUM.ActionEventMapper? = nil,errorEventMapper: RUM.ErrorEventMapper? = nil,longTaskEventMapper: RUM.LongTaskEventMapper? = nil,onSessionStart: RUM.SessionListener? = nil,customEndpoint: URL? = nil,telemetrySampleRate: Float = 20) @@ -1566,6 +1605,17 @@ public static func enable() # API surface for DatadogWebViewTracking: # ---------------------------------- +public final class objc_WebViewTracking: NSObject + public final class SessionReplayConfiguration: NSObject + public enum PrivacyLevel: Int + case allow + case mask + case maskUserInput + @objc public var privacyLevel: PrivacyLevel + override public init() + public init(privacyLevel: PrivacyLevel) + public static func enable(webView: WKWebView,hosts: Set = [],logsSampleRate: Float = 100,with configuration: SessionReplayConfiguration? = nil) + public static func disable(webView: WKWebView) public enum WebViewTracking public struct SessionReplayConfiguration public enum PrivacyLevel: String From 89d9669a9891b49acb276e5ac31031b6c992a230 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 31 May 2024 15:44:37 +0200 Subject: [PATCH 03/71] RUM-2814 Navigate through webviews --- E2ETests/E2ETests.xcodeproj/project.pbxproj | 4 + .../SessionReplayWebView.storyboard | 185 ++++++++++++++++++ .../SessionReplayWebViewController.swift | 39 +++- .../SessionReplayWebViewScenario.swift | 3 +- 4 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebView.storyboard diff --git a/E2ETests/E2ETests.xcodeproj/project.pbxproj b/E2ETests/E2ETests.xcodeproj/project.pbxproj index 6f840fc721..aeac5c7a29 100644 --- a/E2ETests/E2ETests.xcodeproj/project.pbxproj +++ b/E2ETests/E2ETests.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ D2A610B22BE14D01000AA6AB /* DefaultScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A610B12BE14D01000AA6AB /* DefaultScenario.swift */; }; D2C028A02BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */; }; D2C028A22BE14FD300B5D7D3 /* SessionReplayWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */; }; + D2EA0F412C0A075E00CB20F8 /* SessionReplayWebView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2EA0F402C0A075E00CB20F8 /* SessionReplayWebView.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -37,6 +38,7 @@ D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayWebViewScenario.swift; sourceTree = ""; }; D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionReplayWebViewController.swift; sourceTree = ""; }; D2C028A32BE9282400B5D7D3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + D2EA0F402C0A075E00CB20F8 /* SessionReplayWebView.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SessionReplayWebView.storyboard; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -119,6 +121,7 @@ D2C0289E2BE14F1B00B5D7D3 /* SessionReplayWebView */ = { isa = PBXGroup; children = ( + D2EA0F402C0A075E00CB20F8 /* SessionReplayWebView.storyboard */, D2C0289F2BE14F5700B5D7D3 /* SessionReplayWebViewScenario.swift */, D2C028A12BE14FD200B5D7D3 /* SessionReplayWebViewController.swift */, ); @@ -193,6 +196,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D2EA0F412C0A075E00CB20F8 /* SessionReplayWebView.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebView.storyboard b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebView.storyboard new file mode 100644 index 0000000000..b474205ab3 --- /dev/null +++ b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebView.storyboard @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift index 31b7c06b60..605231815a 100644 --- a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift +++ b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewController.swift @@ -27,10 +27,6 @@ class SessionReplayWebViewController: UIViewController, WKUIDelegate { privacyLevel: .allow ) ) - - let url = URL(string: "https://datadoghq.dev/browser-sdk-test-playground/webview-support")! - let request = URLRequest(url: url) - webView.load(request) } func load(url string: String) { @@ -39,3 +35,38 @@ class SessionReplayWebViewController: UIViewController, WKUIDelegate { webView.load(request) } } + +class SessionReplayBasicTextViewController: SessionReplayWebViewController { + override func viewDidLoad() { + super.viewDidLoad() + load(url: "https://datadoghq.dev/browser-sdk-test-playground/webview-support/#basic-text") + } +} + +class SessionReplayImageViewController: SessionReplayWebViewController { + override func viewDidLoad() { + super.viewDidLoad() + load(url: "https://datadoghq.dev/browser-sdk-test-playground/webview-support/#image") + } +} + +class SessionReplayViewPortViewController: SessionReplayWebViewController { + override func viewDidLoad() { + super.viewDidLoad() + load(url: "https://datadoghq.dev/browser-sdk-test-playground/webview-support/#viewport-unit") + } +} + +class SessionReplayShadowDOMViewController: SessionReplayWebViewController { + override func viewDidLoad() { + super.viewDidLoad() + load(url: "https://datadoghq.dev/browser-sdk-test-playground/webview-support/#shadow-dom") + } +} + +class SessionReplayTimestampViewController: SessionReplayWebViewController { + override func viewDidLoad() { + super.viewDidLoad() + load(url: "https://datadoghq.dev/browser-sdk-test-playground/webview-support/#click-event") + } +} diff --git a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift index 4704adbe68..cb681c4dc9 100644 --- a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift +++ b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift @@ -33,6 +33,7 @@ struct SessionReplayWebViewScenario: Scenario { ) ) - return SessionReplayWebViewController() + let storyboard = UIStoryboard(name: "SessionReplayWebView", bundle: nil) + return storyboard.instantiateInitialViewController()! } } From 9d5770e29f812f7682d0104ea86bbc3773519565 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 3 Jun 2024 11:43:27 +0200 Subject: [PATCH 04/71] nav bar snapshot tests --- .../Sources/SRFixtures/Fixtures.swift | 33 ++ .../Storyboards/NavigationBars.storyboard | 547 ++++++++++++++++++ .../SRSnapshotTests/SRHost/AppDelegate.swift | 12 + .../SRHost/MenuViewController.swift | 18 + .../SRSnapshotTests/SRSnapshotTests.swift | 30 + .../Utils/SnapshotTestCase.swift | 15 + .../ViewTreeSnapshot/ViewTreeSnapshot.swift | 2 +- 7 files changed, 656 insertions(+), 1 deletion(-) create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/NavigationBars.storyboard diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift index ecc16c9e88..decdaebf42 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift @@ -33,6 +33,15 @@ public enum Fixture: CaseIterable { /// Instantiated view controller is ``PopupsViewController`` case popups case swiftUI + case navigationBars + case navigationBarDefaultTranslucent + case navigationBarDefaultNonTranslucent + case navigationBarBlackTranslucent + case navigationBarBlackNonTranslucent + case navigationBarDefaultTranslucentBarTint + case navigationBarDefaultNonTranslucentBarTint + case navigationBarDefaultTranslucentBackground + case navigationBarDefaultNonTranslucentBackground public func instantiateViewController() -> UIViewController { switch self { @@ -76,6 +85,29 @@ public enum Fixture: CaseIterable { } else { return ErrorViewController(message: "`.swiftUI` fixture is only available on iOS 13+") } + //- Navigation Bars + case .navigationBars: + return UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "NavigationBars") + case .navigationBarDefaultTranslucent: + let vc = UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Translucent_Navbar") + return vc + case .navigationBarDefaultNonTranslucent: + return UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Non_Translucent_Navbar") + case .navigationBarBlackTranslucent: + let vc = UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Black_Translucent_Navbar") + return vc + case .navigationBarBlackNonTranslucent: + return UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Black_Non_Translucent_Navbar") + case .navigationBarDefaultTranslucentBarTint: + let vc = UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Translucent_Bar_Tint_Navbar") + return vc + case .navigationBarDefaultNonTranslucentBarTint: + return UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Non_Translucent_Bar_Tint_Navbar") + case .navigationBarDefaultTranslucentBackground: + let vc = UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Translucent_Background_Navbar") + return vc + case .navigationBarDefaultNonTranslucentBackground: + return UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Non_Translucent_Background_Navbar") } } } @@ -86,4 +118,5 @@ internal extension UIStoryboard { static var datePickers: UIStoryboard { UIStoryboard(name: "InputElements-DatePickers", bundle: .module) } static var images: UIStoryboard { UIStoryboard(name: "Images", bundle: .module) } static var unsupportedViews: UIStoryboard { UIStoryboard(name: "UnsupportedViews", bundle: .module) } + static var navigationBars: UIStoryboard { UIStoryboard(name: "NavigationBars", bundle: .module) } } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/NavigationBars.storyboard b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/NavigationBars.storyboard new file mode 100644 index 0000000000..e18ac93477 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/NavigationBars.storyboard @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift b/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift index add8925f75..426679156d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift @@ -34,6 +34,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } + func showNavigationController(for fixture: Fixture, completion: @escaping (UINavigationController?) -> Void) { + guard let navigationController = fixture.instantiateViewController() as? UINavigationController else { return completion(nil) } + + // Present it from the next run-loop to avoid "Unbalanced calls to begin/end appearance transitions" warning: + DispatchQueue.main.async { + self.keyWindow?.rootViewController?.dismiss(animated: false) { + self.keyWindow?.rootViewController = navigationController + completion(navigationController) + } + } + } + var keyWindow: UIWindow? { if #available(iOS 15.0, *) { return UIApplication.shared diff --git a/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift b/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift index 07f7866a5b..ee9ab26870 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift @@ -46,6 +46,24 @@ internal extension Fixture { return "Popups" case .swiftUI: return "SwiftUI" + case .navigationBars: + return "Navigation Bars" + case .navigationBarDefaultTranslucent: + return "Embedded Navigation Bar Default + Translucent" + case .navigationBarDefaultNonTranslucent: + return "Embedded Navigation Bar Default + Non Translucent" + case .navigationBarBlackTranslucent: + return "Embedded Navigation Bar Black + Translucent" + case .navigationBarBlackNonTranslucent: + return "Embedded Navigation Bar Black + Non Translucent" + case .navigationBarDefaultTranslucentBarTint: + return "Embedded Navigation Bar Default + Translucent + Bar Tint" + case .navigationBarDefaultNonTranslucentBarTint: + return "Embedded Navigation Bar Default + Non Translucent + Bar Tint" + case .navigationBarDefaultTranslucentBackground: + return "Embedded Navigation Bar Default + Translucent + Background color" + case .navigationBarDefaultNonTranslucentBackground: + return "Embedded Navigation Bar Default + Non Translucent + Background color" } } } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift index a551aeb4aa..4efb9f5b0a 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift @@ -276,4 +276,34 @@ final class SRSnapshotTests: SnapshotTestCase { record: recordingMode ) } + + func testNavigationBars() throws { + + let navBarFixtures: [Fixture] = [ + .navigationBars, + .navigationBarDefaultTranslucent, + .navigationBarDefaultNonTranslucent, + .navigationBarBlackTranslucent, + .navigationBarBlackNonTranslucent, + .navigationBarDefaultTranslucentBarTint, + .navigationBarDefaultNonTranslucentBarTint, + .navigationBarDefaultTranslucentBackground, + .navigationBarDefaultNonTranslucentBackground + ] + + for fixture in navBarFixtures { + show(fixture: fixture) + + try forPrivacyModes([.allow, .mask]) { privacyMode in + let image = try takeSnapshot(with: privacyMode) + let fileNamePrefix = fixture.menuItemTitle.lowercased().replacingOccurrences(of: " ", with: "") + DDAssertSnapshotTest( + newImage: image, + snapshotLocation: .folder(named: snapshotsFolderPath, fileNameSuffix: "-\(fileNamePrefix)-\(privacyMode)-privacy"), + record: recordingMode + ) + } + } + } + } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift index 829c935391..c3911e23d0 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift @@ -35,6 +35,21 @@ internal class SnapshotTestCase: XCTestCase { return viewController } + func showNavigationController(for fixture: Fixture) -> UINavigationController? { + let expectation = self.expectation(description: "Wait for navigation controller being shown") + + var navigationController: UINavigationController? + + app.showNavigationController(for: fixture) { + navigationController = $0 + expectation.fulfill() + } + + waitForExpectations(timeout: 30) // very pessimistic timeout to mitigate CI lags + + return navigationController + } + /// Captures side-by-side snapshot of the app UI and recorded wireframes. func takeSnapshot(with privacyLevel: SessionReplay.Configuration.PrivacyLevel = defaultPrivacyLevel) throws -> UIImage { let expectWireframes = self.expectation(description: "Wait for wireframes") diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshot.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshot.swift index 0f1f7b35b6..e679df286b 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshot.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshot.swift @@ -78,7 +78,7 @@ public struct SessionReplayViewAttributes: Equatable { /// The view's `frame`, in VTS's root view's coordinate space (usually, the screen coordinate space). public let frame: CGRect - /// Original view's `.backgorundColor`. + /// Original view's `.backgroundColor`. public let backgroundColor: CGColor? /// Original view's `layer.borderColor`. From 08424b786169cd3e9bfd4fb020009c2c0e78b9e6 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 3 Jun 2024 16:14:39 +0200 Subject: [PATCH 05/71] Adding new snapshot hashes --- ...eddednavigationbarblack+nontranslucent-allow-privacy.png.json | 1 + ...beddednavigationbarblack+nontranslucent-mask-privacy.png.json | 1 + ...embeddednavigationbarblack+translucent-allow-privacy.png.json | 1 + ...-embeddednavigationbarblack+translucent-mask-privacy.png.json | 1 + ...default+nontranslucent+backgroundcolor-allow-privacy.png.json | 1 + ...rdefault+nontranslucent+backgroundcolor-mask-privacy.png.json | 1 + ...ationbardefault+nontranslucent+bartint-allow-privacy.png.json | 1 + ...gationbardefault+nontranslucent+bartint-mask-privacy.png.json | 1 + ...dednavigationbardefault+nontranslucent-allow-privacy.png.json | 1 + ...ddednavigationbardefault+nontranslucent-mask-privacy.png.json | 1 + ...bardefault+translucent+backgroundcolor-allow-privacy.png.json | 1 + ...nbardefault+translucent+backgroundcolor-mask-privacy.png.json | 1 + ...vigationbardefault+translucent+bartint-allow-privacy.png.json | 1 + ...avigationbardefault+translucent+bartint-mask-privacy.png.json | 1 + ...beddednavigationbardefault+translucent-allow-privacy.png.json | 1 + ...mbeddednavigationbardefault+translucent-mask-privacy.png.json | 1 + .../testNavigationBars()-navigationbars-allow-privacy.png.json | 1 + .../testNavigationBars()-navigationbars-mask-privacy.png.json | 1 + 18 files changed, 18 insertions(+) create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json new file mode 100644 index 0000000000..84420cc956 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"91655f673922fa0fae6b84b3493271755294ce66"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json new file mode 100644 index 0000000000..d6d9ed125c --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"01f4c03dafd36ce83db11cf2e1207307c2e0953b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json new file mode 100644 index 0000000000..51e6e2a2c1 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"6ab2e5849bcb2116449a733bf5577a095f27f464"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json new file mode 100644 index 0000000000..6deecc5d83 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"b84fa4421c85e75a3be0adf36f56d4acc220d84f"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json new file mode 100644 index 0000000000..b1ae26ee6f --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"bfc1bbf09330ed51e577daf4c54f7d084f3a7548"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json new file mode 100644 index 0000000000..aa6effe3e0 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"9353558262f7c7d5483f86fbf8357476047c09b4"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json new file mode 100644 index 0000000000..465d4e71ac --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"54346586196e5c3a1add7d5ab45e0dd5709b4341"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json new file mode 100644 index 0000000000..6529fc4c7a --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"c8900122256dac31df15886a19ecf7143e6904de"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json new file mode 100644 index 0000000000..b1c1a62f5f --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"9be3b87cd188d1cd3fb73076adf1d643aac13a0c"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json new file mode 100644 index 0000000000..f67060ae4b --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"b0b644feb2c3de1ce6fbe5c1fcc52cb2b0fb76ec"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json new file mode 100644 index 0000000000..c3fba1cf78 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"153fa612844cf744a7dd4b649927457671ad7c6b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json new file mode 100644 index 0000000000..bdc076a01c --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"4ddbec75cbbb65a24615ebbeed7f2f84b22c1ab5"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json new file mode 100644 index 0000000000..2d8bb579c7 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"f128edd2df5ee16f3d6ab267b70c56b0a0aecbac"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json new file mode 100644 index 0000000000..6ffbde8ce5 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"f01f685e3c62d1f7e70343cb17d5d75456d6c6b5"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json new file mode 100644 index 0000000000..fc32693f45 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"3963e0881cdada333af7169e14d845ab6c0de6c7"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json new file mode 100644 index 0000000000..6503e0e1e1 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"6572e6ab36ad61a0df026868a022223b5b23a51b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json new file mode 100644 index 0000000000..03c7e671c2 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"7a636c7232e031151972b60a635fc028584be59e"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json new file mode 100644 index 0000000000..aae8c069fe --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"3919dcc283427b76c84faa993719f6a9533a5f09"} \ No newline at end of file From 5360babcccf7767f7135a3edec398e2eeb9ea572 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 3 Jun 2024 16:41:47 +0200 Subject: [PATCH 06/71] Fix GH asset otel validation --- .../src/release/assets/gh_asset.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tools/distribution/src/release/assets/gh_asset.py b/tools/distribution/src/release/assets/gh_asset.py index bc840d2c12..344b5bd8b4 100644 --- a/tools/distribution/src/release/assets/gh_asset.py +++ b/tools/distribution/src/release/assets/gh_asset.py @@ -167,7 +167,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool if in_version < min_cr_version: return False # Datadog Crash Reporting.xcframework was introduced in `1.7.0` - dir = zip_directory.get('CrashReporter.xcframework') + dir = zip_directory.get(self.name) min_xc14_version = Version('1.12.1') if in_version >= min_xc14_version: @@ -204,7 +204,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool if in_version < min_version or in_version >= max_version: return False - zip_directory.get('Kronos.xcframework').assert_it_has_files([ + zip_directory.get(self.name).assert_it_has_files([ 'ios-arm64_arm64e', 'ios-arm64_arm64e/dSYMs/*.dSYM', 'ios-arm64_arm64e/**/*.swiftinterface', @@ -251,6 +251,33 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool ]) return True + +class OpenTelemetryXCFrameworkValidator(XCFrameworkValidator): + name = 'OpenTelemetryApi.xcframework' + + def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: + min_otel_version = Version('2.12.0') + if in_version < min_otel_version: + return False # introduced in 2.12.0 + + dir = zip_directory.get(self.name) + + dir.assert_it_has_files([ + 'ios-arm64_x86_64-simulator', + 'ios-arm64_x86_64-simulator/dSYMs/*.dSYM', + 'ios-arm64_x86_64-simulator/**/*.swiftinterface', + ]) + + dir.assert_it_has_files([ + 'tvos-arm64', + 'tvos-arm64/**/*.swiftinterface', + + 'tvos-arm64_x86_64-simulator', + 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', + 'tvos-arm64_x86_64-simulator/**/*.swiftinterface', + ]) + + return True xcframeworks_validators: list[XCFrameworkValidator] = [ DatadogXCFrameworkValidator(), @@ -268,6 +295,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool DatadogObjcXCFrameworkValidator(), DatadogCrashReportingXCFrameworkValidator(), CrashReporterXCFrameworkValidator(), + OpenTelemetryXCFrameworkValidator(), ] class GHAsset: From 439e21152d38405b1d1abf9ab9f22029e4315840 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Tue, 4 Jun 2024 10:29:27 +0200 Subject: [PATCH 07/71] Remove unused functions --- .../SRSnapshotTests/SRHost/AppDelegate.swift | 12 ------------ .../SRSnapshotTests/Utils/SnapshotTestCase.swift | 15 --------------- 2 files changed, 27 deletions(-) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift b/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift index 426679156d..add8925f75 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRHost/AppDelegate.swift @@ -34,18 +34,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - func showNavigationController(for fixture: Fixture, completion: @escaping (UINavigationController?) -> Void) { - guard let navigationController = fixture.instantiateViewController() as? UINavigationController else { return completion(nil) } - - // Present it from the next run-loop to avoid "Unbalanced calls to begin/end appearance transitions" warning: - DispatchQueue.main.async { - self.keyWindow?.rootViewController?.dismiss(animated: false) { - self.keyWindow?.rootViewController = navigationController - completion(navigationController) - } - } - } - var keyWindow: UIWindow? { if #available(iOS 15.0, *) { return UIApplication.shared diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift index c3911e23d0..829c935391 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift @@ -35,21 +35,6 @@ internal class SnapshotTestCase: XCTestCase { return viewController } - func showNavigationController(for fixture: Fixture) -> UINavigationController? { - let expectation = self.expectation(description: "Wait for navigation controller being shown") - - var navigationController: UINavigationController? - - app.showNavigationController(for: fixture) { - navigationController = $0 - expectation.fulfill() - } - - waitForExpectations(timeout: 30) // very pessimistic timeout to mitigate CI lags - - return navigationController - } - /// Captures side-by-side snapshot of the app UI and recorded wireframes. func takeSnapshot(with privacyLevel: SessionReplay.Configuration.PrivacyLevel = defaultPrivacyLevel) throws -> UIImage { let expectWireframes = self.expectation(description: "Wait for wireframes") From c8bb9bc9c993e1fbe5640323422fd303c40a30de Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 4 Jun 2024 11:23:54 +0200 Subject: [PATCH 08/71] Update gh_asset.py --- tools/distribution/src/release/assets/gh_asset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/distribution/src/release/assets/gh_asset.py b/tools/distribution/src/release/assets/gh_asset.py index 344b5bd8b4..0a4971416c 100644 --- a/tools/distribution/src/release/assets/gh_asset.py +++ b/tools/distribution/src/release/assets/gh_asset.py @@ -32,7 +32,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool if in_version >= v2: return False # Datadog.xcframework no longer exist in `2.0` - dir = zip_directory.get('Datadog.xcframework') + dir = zip_directory.get(self.name) # above 1.12.1: framework includes arm64e slices min_arm64e_version = Version('1.12.1') @@ -77,7 +77,7 @@ class DatadogObjcXCFrameworkValidator(XCFrameworkValidator): def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: # always expect `DatadogObjc.xcframework` - dir = zip_directory.get('DatadogObjc.xcframework') + dir = zip_directory.get(self.name) # above 1.12.1: framework includes arm64e slices min_arm64e_version = Version('1.12.1') @@ -122,7 +122,7 @@ def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool if in_version < min_cr_version: return False # Datadog Crash Reporting.xcframework was introduced in `1.7.0` - dir = zip_directory.get('DatadogCrashReporting.xcframework') + dir = zip_directory.get(self.name) # above 1.12.1: framework includes arm64e slices min_arm64e_version = Version('1.12.1') From d6ff2b36456eb166d54ae53bc9e8524d93af67d5 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Mon, 27 May 2024 12:11:34 +0100 Subject: [PATCH 09/71] RUM-917 Improve telemetry of type mismatch --- .../Sources/Models/SRDataModels.swift | 15 +++++++++ .../Processor/Diffing/Diff+SRWireframes.swift | 31 ++++++++++++++----- .../Builders/RecordsBuilderTests.swift | 2 +- .../Diffing/Diff+SRWireframesTests.swift | 8 ++++- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/DatadogSessionReplay/Sources/Models/SRDataModels.swift b/DatadogSessionReplay/Sources/Models/SRDataModels.swift index fbc7613f95..b21dc0f8d0 100644 --- a/DatadogSessionReplay/Sources/Models/SRDataModels.swift +++ b/DatadogSessionReplay/Sources/Models/SRDataModels.swift @@ -497,6 +497,21 @@ public enum SRWireframe: Codable { case placeholderWireframe(value: SRPlaceholderWireframe) case webviewWireframe(value: SRWebviewWireframe) + var type: String { + switch self { + case let (.shapeWireframe(value)): + return value.type + case let (.textWireframe(value)): + return value.type + case let (.imageWireframe(value)): + return value.type + case let (.placeholderWireframe(value)): + return value.type + case let (.webviewWireframe(value)): + return value.type + } + } + // MARK: - Codable public func encode(to encoder: Encoder) throws { diff --git a/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift b/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift index c5e197ba1a..f455976a38 100644 --- a/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift +++ b/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift @@ -47,11 +47,11 @@ extension SRWireframe: Diffable { internal typealias WireframeMutation = SRIncrementalSnapshotRecord.Data.MutationData.Updates -internal enum WireframeMutationError: Error { +internal enum WireframeMutationError: Error, Equatable { /// Indicates an attempt of computing mutation for wireframes that have different `id`. case idMismatch /// Indicates an attempt of computing mutation for wireframes that have different type. - case typeMismatch + case typeMismatch(fromType: String, toType: String) } internal protocol MutableWireframe { @@ -84,8 +84,13 @@ extension SRWireframe: MutableWireframe { extension SRShapeWireframe: MutableWireframe { func mutations(from otherWireframe: SRWireframe) throws -> WireframeMutation { guard case .shapeWireframe(let other) = otherWireframe else { - throw WireframeMutationError.typeMismatch + throw WireframeMutationError.typeMismatch( + fromType: otherWireframe.type, + toType: type + ) } + // print string of enum of otherWireframe + guard other.id == id else { throw WireframeMutationError.idMismatch } @@ -108,7 +113,10 @@ extension SRShapeWireframe: MutableWireframe { extension SRPlaceholderWireframe: MutableWireframe { func mutations(from otherWireframe: SRWireframe) throws -> WireframeMutation { guard case .placeholderWireframe(let other) = otherWireframe else { - throw WireframeMutationError.typeMismatch + throw WireframeMutationError.typeMismatch( + fromType: otherWireframe.type, + toType: type + ) } guard other.id == id else { throw WireframeMutationError.idMismatch @@ -130,7 +138,10 @@ extension SRPlaceholderWireframe: MutableWireframe { extension SRImageWireframe: MutableWireframe { func mutations(from otherWireframe: SRWireframe) throws -> WireframeMutation { guard case .imageWireframe(let other) = otherWireframe else { - throw WireframeMutationError.typeMismatch + throw WireframeMutationError.typeMismatch( + fromType: otherWireframe.type, + toType: type + ) } guard other.id == id else { throw WireframeMutationError.idMismatch @@ -157,7 +168,10 @@ extension SRImageWireframe: MutableWireframe { extension SRTextWireframe: MutableWireframe { func mutations(from otherWireframe: SRWireframe) throws -> WireframeMutation { guard case .textWireframe(let other) = otherWireframe else { - throw WireframeMutationError.typeMismatch + throw WireframeMutationError.typeMismatch( + fromType: otherWireframe.type, + toType: type + ) } guard other.id == id else { throw WireframeMutationError.idMismatch @@ -184,7 +198,10 @@ extension SRTextWireframe: MutableWireframe { extension SRWebviewWireframe: MutableWireframe { func mutations(from otherWireframe: SRWireframe) throws -> WireframeMutation { guard case .webviewWireframe(let other) = otherWireframe else { - throw WireframeMutationError.typeMismatch + throw WireframeMutationError.typeMismatch( + fromType: otherWireframe.type, + toType: type + ) } guard other.id == id, other.slotId == slotId else { throw WireframeMutationError.idMismatch diff --git a/DatadogSessionReplay/Tests/Processor/Builders/RecordsBuilderTests.swift b/DatadogSessionReplay/Tests/Processor/Builders/RecordsBuilderTests.swift index 73a7287e69..e007841e51 100644 --- a/DatadogSessionReplay/Tests/Processor/Builders/RecordsBuilderTests.swift +++ b/DatadogSessionReplay/Tests/Processor/Builders/RecordsBuilderTests.swift @@ -97,7 +97,7 @@ class RecordsBuilderTests: XCTestCase { telemetry.description, """ Telemetry logs: - - [error] [SR] Failed to create incremental record - typeMismatch, kind: WireframeMutationError, stack: typeMismatch + - [error] [SR] Failed to create incremental record - typeMismatch(fromType: "shape", toType: "text"), kind: WireframeMutationError, stack: typeMismatch(fromType: "shape", toType: "text") """ ) } diff --git a/DatadogSessionReplay/Tests/Processor/Diffing/Diff+SRWireframesTests.swift b/DatadogSessionReplay/Tests/Processor/Diffing/Diff+SRWireframesTests.swift index 28bd93a6c5..6dc326c4d9 100644 --- a/DatadogSessionReplay/Tests/Processor/Diffing/Diff+SRWireframesTests.swift +++ b/DatadogSessionReplay/Tests/Processor/Diffing/Diff+SRWireframesTests.swift @@ -117,7 +117,13 @@ class DiffSRWireframes: XCTestCase { try wireframes.forEach { wireframeA, wireframeB in XCTAssertThrowsError(try wireframeB.mutations(from: wireframeA)) { error in // Then - XCTAssertEqual(error as? WireframeMutationError, WireframeMutationError.typeMismatch) + XCTAssertEqual( + error as? WireframeMutationError, + WireframeMutationError.typeMismatch( + fromType: wireframeA.type, + toType: wireframeB.type + ) + ) } } } From 566edef3e0683277755611d6a47500a8a8db0dcf Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 4 Jun 2024 15:40:53 +0200 Subject: [PATCH 10/71] RUM-917 Update model --- .../Sources/Models/SRDataModels.swift | 15 --------------- .../Processor/Diffing/Diff+SRWireframes.swift | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/DatadogSessionReplay/Sources/Models/SRDataModels.swift b/DatadogSessionReplay/Sources/Models/SRDataModels.swift index b21dc0f8d0..fbc7613f95 100644 --- a/DatadogSessionReplay/Sources/Models/SRDataModels.swift +++ b/DatadogSessionReplay/Sources/Models/SRDataModels.swift @@ -497,21 +497,6 @@ public enum SRWireframe: Codable { case placeholderWireframe(value: SRPlaceholderWireframe) case webviewWireframe(value: SRWebviewWireframe) - var type: String { - switch self { - case let (.shapeWireframe(value)): - return value.type - case let (.textWireframe(value)): - return value.type - case let (.imageWireframe(value)): - return value.type - case let (.placeholderWireframe(value)): - return value.type - case let (.webviewWireframe(value)): - return value.type - } - } - // MARK: - Codable public func encode(to encoder: Encoder) throws { diff --git a/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift b/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift index f455976a38..d4e53e2545 100644 --- a/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift +++ b/DatadogSessionReplay/Sources/Processor/Diffing/Diff+SRWireframes.swift @@ -41,6 +41,21 @@ extension SRWireframe: Diffable { return true } } + + var type: String { + switch self { + case let (.shapeWireframe(value)): + return value.type + case let (.textWireframe(value)): + return value.type + case let (.imageWireframe(value)): + return value.type + case let (.placeholderWireframe(value)): + return value.type + case let (.webviewWireframe(value)): + return value.type + } + } } // MARK: - Resolving Mutations From fc54e4424ce521429d2d22be231548ae8ecf70f6 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 4 Jun 2024 10:03:10 +0200 Subject: [PATCH 11/71] RUM-4632 Share SR config --- Datadog/Datadog.xcodeproj/project.pbxproj | 20 +++++++++++ DatadogCore/Sources/Core/DatadogCore.swift | 4 +-- .../DatadogInternal/DatadogCoreProxy.swift | 4 +-- .../Sources/DatadogCoreProtocol.swift | 15 ++++++-- .../SessionReplayConfiguration.swift | 35 +++++++++++++++++++ .../SessionReplay/SessionReplay+objc.swift | 6 ++-- .../Feature/SessionReplayFeature.swift | 4 ++- .../Sources/Recorder/PrivacyLevel.swift | 7 ++-- .../Sources/SessionReplay+objc.swift | 6 ++-- .../Sources/SessionReplayConfiguration.swift | 16 ++------- .../Tests/Mocks/RecorderMocks.swift | 10 ------ .../Sources/ObjC/WebViewTracking+objc.swift | 4 ++- .../Sources/WebViewTracking.swift | 27 ++++++-------- DatadogWebViewTracking/Tests/Mocks.swift | 10 ------ .../FeatureRegistrationCoreMock.swift | 2 +- .../Mocks/CoreMocks/PassthroughCoreMock.swift | 2 +- .../CoreMocks/SingleFeatureCoreMock.swift | 2 +- .../SessionReplayConfigurationMocks.swift | 18 ++++++++++ 18 files changed, 122 insertions(+), 70 deletions(-) create mode 100644 DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift create mode 100644 TestUtilities/Mocks/FeatureModels/SessionReplayConfigurationMocks.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index b1768c336c..dba48a02b1 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1367,6 +1367,9 @@ D2C5D5302B84F71200B63F36 /* WebRecordIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C5D52F2B84F71200B63F36 /* WebRecordIntegrationTests.swift */; }; D2C7E3AB28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */; }; D2C7E3AE28FEBDA10023B2CC /* LaunchTimePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */; }; + D2C9A26A2C0F3F5A007526F5 /* SessionReplayConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EA0F452C0E1AE200CB20F8 /* SessionReplayConfiguration.swift */; }; + D2C9A2872C0F467C007526F5 /* SessionReplayConfigurationMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C9A2852C0F4660007526F5 /* SessionReplayConfigurationMocks.swift */; }; + D2C9A2882C0F467C007526F5 /* SessionReplayConfigurationMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C9A2852C0F4660007526F5 /* SessionReplayConfigurationMocks.swift */; }; D2CB6E0C27C50EAE00A62B57 /* DatadogCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 61133B85242393DE00786299 /* DatadogCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2CB6E0D27C50EAE00A62B57 /* ObjcAppLaunchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2CB6E0E27C50EAE00A62B57 /* ObjcExceptionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E68FB54244707FD0013A8AA /* ObjcExceptionHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1576,6 +1579,7 @@ D2DC4BF727F484AA00E4FB96 /* DataEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DC4BF527F484AA00E4FB96 /* DataEncryption.swift */; }; D2DE63532A30A7CA00441A54 /* CoreRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DE63522A30A7CA00441A54 /* CoreRegistry.swift */; }; D2DE63542A30A7CA00441A54 /* CoreRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DE63522A30A7CA00441A54 /* CoreRegistry.swift */; }; + D2EA0F462C0E1AE300CB20F8 /* SessionReplayConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EA0F452C0E1AE200CB20F8 /* SessionReplayConfiguration.swift */; }; D2EBEE1F29BA160F00B15732 /* HTTPHeadersReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E13A92524B8700098C6B0 /* HTTPHeadersReader.swift */; }; D2EBEE2029BA160F00B15732 /* TracePropagationHeadersWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EBEDCF29B8A02100B15732 /* TracePropagationHeadersWriter.swift */; }; D2EBEE2129BA160F00B15732 /* W3CHTTPHeaders.swift in Sources */ = {isa = PBXBuildFile; fileRef = A728AD9C2934CE4400397996 /* W3CHTTPHeaders.swift */; }; @@ -2908,6 +2912,7 @@ D2C5D52F2B84F71200B63F36 /* WebRecordIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebRecordIntegrationTests.swift; sourceTree = ""; }; D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryStatusPublisherTests.swift; sourceTree = ""; }; D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTimePublisher.swift; sourceTree = ""; }; + D2C9A2852C0F4660007526F5 /* SessionReplayConfigurationMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayConfigurationMocks.swift; sourceTree = ""; }; D2CB6ED127C50EAE00A62B57 /* DatadogCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2CB6F8F27C520D400A62B57 /* DatadogCoreTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogCoreTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D2CB6FB027C5217A00A62B57 /* DatadogObjc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogObjc.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -2935,6 +2940,7 @@ D2DC4BF527F484AA00E4FB96 /* DataEncryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataEncryption.swift; sourceTree = ""; }; D2DE63522A30A7CA00441A54 /* CoreRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreRegistry.swift; sourceTree = ""; }; D2E8D59728C7AB90007E5DE1 /* ContextMessageReceiverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMessageReceiverTests.swift; sourceTree = ""; }; + D2EA0F452C0E1AE200CB20F8 /* SessionReplayConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionReplayConfiguration.swift; sourceTree = ""; }; D2EBEDCC29B893D800B15732 /* TraceID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceID.swift; sourceTree = ""; }; D2EBEDCF29B8A02100B15732 /* TracePropagationHeadersWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracePropagationHeadersWriter.swift; sourceTree = ""; }; D2EBEDD229B8A58E00B15732 /* TracePropagationHeadersReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracePropagationHeadersReader.swift; sourceTree = ""; }; @@ -4669,6 +4675,7 @@ 6167E6DF2B81203A00C3CA2D /* Models */ = { isa = PBXGroup; children = ( + D2EA0F442C0E1A8700CB20F8 /* SessionReplay */, 619F5CEB2BF5089B004BFE70 /* RUM */, D25C834D2B88A261008E73B1 /* WebViewTracking */, 6167E6E02B81204B00C3CA2D /* CrashReporting */, @@ -4700,6 +4707,7 @@ isa = PBXGroup; children = ( 6167E7172B837F6300C3CA2D /* CrashReporting */, + D2C9A2852C0F4660007526F5 /* SessionReplayConfigurationMocks.swift */, ); path = FeatureModels; sourceTree = ""; @@ -6234,6 +6242,14 @@ path = Context; sourceTree = ""; }; + D2EA0F442C0E1A8700CB20F8 /* SessionReplay */ = { + isa = PBXGroup; + children = ( + D2EA0F452C0E1AE200CB20F8 /* SessionReplayConfiguration.swift */, + ); + path = SessionReplay; + sourceTree = ""; + }; D2EBEE1D29BA15BC00B15732 /* NetworkInstrumentation */ = { isa = PBXGroup; children = ( @@ -8520,6 +8536,7 @@ D23039E5298D5236001A1FA3 /* DateProvider.swift in Sources */, D23039E0298D5235001A1FA3 /* DatadogCoreProtocol.swift in Sources */, D23039FD298D5236001A1FA3 /* DataCompression.swift in Sources */, + D2EA0F462C0E1AE300CB20F8 /* SessionReplayConfiguration.swift in Sources */, 6167E6F92B81E95900C3CA2D /* BinaryImage.swift in Sources */, 6174D60C2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */, D23039F0298D5236001A1FA3 /* AnyEncoder.swift in Sources */, @@ -8709,6 +8726,7 @@ D2F44FBC299AA36D0074B0D9 /* Decompression.swift in Sources */, D24C9C5229A7BD12002057CF /* SamplerMock.swift in Sources */, D2579557298ABB04008A1BE5 /* AttributesMocks.swift in Sources */, + D2C9A2872C0F467C007526F5 /* SessionReplayConfigurationMocks.swift in Sources */, 6167E7292B84C11900C3CA2D /* DDCrashReportMocks.swift in Sources */, D2DA23C7298D5AC000C6C7E6 /* TelemetryMocks.swift in Sources */, 613F9C1B2BB03188007C7606 /* FeatureScopeMock.swift in Sources */, @@ -8755,6 +8773,7 @@ D2F44FBD299AA36D0074B0D9 /* Decompression.swift in Sources */, D24C9C5329A7BD12002057CF /* SamplerMock.swift in Sources */, D257957F298ABB83008A1BE5 /* AttributesMocks.swift in Sources */, + D2C9A2882C0F467C007526F5 /* SessionReplayConfigurationMocks.swift in Sources */, 6167E72A2B84C11900C3CA2D /* DDCrashReportMocks.swift in Sources */, D2DA23C8298D5AC000C6C7E6 /* TelemetryMocks.swift in Sources */, 613F9C1C2BB03188007C7606 /* FeatureScopeMock.swift in Sources */, @@ -9452,6 +9471,7 @@ D2DA237B298D57AA00C6C7E6 /* DateProvider.swift in Sources */, D2DA237C298D57AA00C6C7E6 /* DatadogCoreProtocol.swift in Sources */, D2DA237D298D57AA00C6C7E6 /* DataCompression.swift in Sources */, + D2C9A26A2C0F3F5A007526F5 /* SessionReplayConfiguration.swift in Sources */, 6167E6FA2B81E95900C3CA2D /* BinaryImage.swift in Sources */, 6174D60D2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */, D2DA237E298D57AA00C6C7E6 /* AnyEncoder.swift in Sources */, diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 98c80e516d..a65d0b89cd 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -292,8 +292,8 @@ extension DatadogCore: DatadogCoreProtocol { /// - name: The Feature's name. /// - type: The Feature instance type. /// - Returns: The Feature if any. - func get(feature type: T.Type = T.self) -> T? where T: DatadogFeature { - features[T.name] as? T + func feature(named name: String, type: T.Type) -> T? { + features[name] as? T } func scope(for featureType: Feature.Type) -> FeatureScope where Feature: DatadogFeature { diff --git a/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift b/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift index fb810502ce..809b84f07c 100644 --- a/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift +++ b/DatadogCore/Tests/Datadog/Mocks/DatadogInternal/DatadogCoreProxy.swift @@ -70,8 +70,8 @@ internal class DatadogCoreProxy: DatadogCoreProtocol { try core.register(feature: feature) } - func get(feature type: T.Type) -> T? where T: DatadogFeature { - return core.get(feature: type) + func feature(named name: String, type: T.Type) -> T? { + return core.feature(named: name, type: type) } func scope(for featureType: T.Type) -> FeatureScope where T: DatadogFeature { diff --git a/DatadogInternal/Sources/DatadogCoreProtocol.swift b/DatadogInternal/Sources/DatadogCoreProtocol.swift index 358e30b29d..c9655e8192 100644 --- a/DatadogInternal/Sources/DatadogCoreProtocol.swift +++ b/DatadogInternal/Sources/DatadogCoreProtocol.swift @@ -38,7 +38,7 @@ public protocol DatadogCoreProtocol: AnyObject, MessageSending, BaggageSharing { /// - name: The Feature's name. /// - type: The Feature instance type. /// - Returns: The Feature if any. - func get(feature type: T.Type) -> T? where T: DatadogFeature + func feature(named name: String, type: T.Type) -> T? /// Retrieves a Feature Scope for given feature type. /// @@ -100,6 +100,17 @@ public protocol BaggageSharing { func set(baggage: @escaping () -> FeatureBaggage?, forKey key: String) } +extension DatadogCoreProtocol { + /// Returns a `DatadogFeature` conforming type from the + /// Feature registry. + /// + /// - Parameter type: The Feature instance type. + /// - Returns: The Feature if any. + public func get(feature type: T.Type = T.self) -> T? where T: DatadogFeature { + feature(named: T.name, type: type) + } +} + extension MessageSending { /// Sends a message on the bus shared by features registered to the same core. /// @@ -290,7 +301,7 @@ public class NOPDatadogCore: DatadogCoreProtocol { /// no-op public func register(feature: T) throws where T: DatadogFeature { } /// no-op - public func get(feature type: T.Type) -> T? where T: DatadogFeature { nil } + public func feature(named name: String, type: T.Type) -> T? { nil } /// no-op public func scope(for featureType: T.Type) -> FeatureScope { NOPFeatureScope() } /// no-op diff --git a/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift b/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift new file mode 100644 index 0000000000..fc7918f124 --- /dev/null +++ b/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift @@ -0,0 +1,35 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + +/// Available privacy levels for content masking in Session Replay. +public enum SessionReplayPrivacyLevel: String { + /// Record all content. + case allow + + /// Mask all content. + case mask + + /// Mask input elements, but record all other content. + case maskUserInput = "mask_user_input" +} + +/// The Session Replay shared configuration. +/// +/// The Feature object named `session-replay` will be registered to the core +/// when enabling Session Replay. If available, the configuration can be retreived +/// with: +/// +/// let sessionReplay = core.feature( +/// named: "session-replay", +/// type: SessionReplayConfiguration.self +/// ) +/// +public protocol SessionReplayConfiguration { + /// The privacy level to use for the web view replay recording. + var privacyLevel: SessionReplayPrivacyLevel { get } +} diff --git a/DatadogObjc/Sources/SessionReplay/SessionReplay+objc.swift b/DatadogObjc/Sources/SessionReplay/SessionReplay+objc.swift index 94402dc243..66e1285545 100644 --- a/DatadogObjc/Sources/SessionReplay/SessionReplay+objc.swift +++ b/DatadogObjc/Sources/SessionReplay/SessionReplay+objc.swift @@ -6,6 +6,8 @@ import Foundation #if os(iOS) + +import DatadogInternal import DatadogSessionReplay /// An entry point to Datadog Session Replay feature. @@ -89,7 +91,7 @@ public enum DDSessionReplayConfigurationPrivacyLevel: Int { /// Mask input elements, but record all other content. case maskUserInput - internal var _swift: SessionReplay.Configuration.PrivacyLevel { + internal var _swift: SessionReplayPrivacyLevel { switch self { case .allow: return .allow case .mask: return .mask @@ -98,7 +100,7 @@ public enum DDSessionReplayConfigurationPrivacyLevel: Int { } } - internal init(_ swift: SessionReplay.Configuration.PrivacyLevel) { + internal init(_ swift: SessionReplayPrivacyLevel) { switch swift { case .allow: self = .allow case .mask: self = .mask diff --git a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift index 389b095f9b..6ef82afa22 100644 --- a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift +++ b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift @@ -8,12 +8,13 @@ import Foundation import DatadogInternal -internal class SessionReplayFeature: DatadogRemoteFeature { +internal class SessionReplayFeature: SessionReplayConfiguration, DatadogRemoteFeature { static let name: String = "session-replay" let requestBuilder: FeatureRequestBuilder let messageReceiver: FeatureMessageReceiver let performanceOverride: PerformancePresetOverride? + let privacyLevel: SessionReplayPrivacyLevel // MARK: - Main Components @@ -46,6 +47,7 @@ internal class SessionReplayFeature: DatadogRemoteFeature { let scheduler = MainThreadScheduler(interval: 0.1) let contextReceiver = RUMContextReceiver() + self.privacyLevel = configuration.defaultPrivacyLevel self.messageReceiver = CombinedFeatureMessageReceiver([ contextReceiver, WebViewRecordReceiver( diff --git a/DatadogSessionReplay/Sources/Recorder/PrivacyLevel.swift b/DatadogSessionReplay/Sources/Recorder/PrivacyLevel.swift index 0c44ca3980..a13f8ac262 100644 --- a/DatadogSessionReplay/Sources/Recorder/PrivacyLevel.swift +++ b/DatadogSessionReplay/Sources/Recorder/PrivacyLevel.swift @@ -5,14 +5,13 @@ */ #if os(iOS) -@_spi(Internal) -public typealias SessionReplayPrivacyLevel = SessionReplay.Configuration.PrivacyLevel +import DatadogInternal internal typealias PrivacyLevel = SessionReplayPrivacyLevel /// Text obfuscation strategies for different text types. @_spi(Internal) -public extension SessionReplay.Configuration.PrivacyLevel { +public extension PrivacyLevel { /// Returns "Sensitive Text" obfuscator for given `privacyLevel`. /// /// In Session Replay, "Sensitive Text" is: @@ -59,7 +58,7 @@ public extension SessionReplay.Configuration.PrivacyLevel { } /// Other convenience helpers. -internal extension SessionReplay.Configuration.PrivacyLevel { +internal extension SessionReplayPrivacyLevel { /// If input elements should be masked in this privacy mode. var shouldMaskInputElements: Bool { switch self { diff --git a/DatadogSessionReplay/Sources/SessionReplay+objc.swift b/DatadogSessionReplay/Sources/SessionReplay+objc.swift index 2a106f811e..b860adea2e 100644 --- a/DatadogSessionReplay/Sources/SessionReplay+objc.swift +++ b/DatadogSessionReplay/Sources/SessionReplay+objc.swift @@ -5,6 +5,8 @@ */ import Foundation +import DatadogInternal + #if os(iOS) /// An entry point to Datadog Session Replay feature. @@ -87,7 +89,7 @@ public enum DDSessionReplayConfigurationPrivacyLevel: Int { /// Mask input elements, but record all other content. case maskUserInput - internal var _swift: SessionReplay.Configuration.PrivacyLevel { + internal var _swift: SessionReplayPrivacyLevel { switch self { case .allow: return .allow case .mask: return .mask @@ -96,7 +98,7 @@ public enum DDSessionReplayConfigurationPrivacyLevel: Int { } } - internal init(_ swift: SessionReplay.Configuration.PrivacyLevel) { + internal init(_ swift: SessionReplayPrivacyLevel) { switch swift { case .allow: self = .allow case .mask: self = .mask diff --git a/DatadogSessionReplay/Sources/SessionReplayConfiguration.swift b/DatadogSessionReplay/Sources/SessionReplayConfiguration.swift index cb0a3f16e3..b86a2a98bc 100644 --- a/DatadogSessionReplay/Sources/SessionReplayConfiguration.swift +++ b/DatadogSessionReplay/Sources/SessionReplayConfiguration.swift @@ -24,19 +24,7 @@ extension SessionReplay { /// Defines the way sensitive content (e.g. text) should be masked. /// /// Default: `.mask`. - public var defaultPrivacyLevel: PrivacyLevel - - /// Available privacy levels for content masking. - public enum PrivacyLevel: String { - /// Record all content. - case allow - - /// Mask all content. - case mask - - /// Mask input elements, but record all other content. - case maskUserInput = "mask_user_input" - } + public var defaultPrivacyLevel: SessionReplayPrivacyLevel /// Custom server url for sending replay data. /// @@ -56,7 +44,7 @@ extension SessionReplay { /// - customEndpoint: Custom server url for sending replay data. Default: `nil`. public init( replaySampleRate: Float, - defaultPrivacyLevel: PrivacyLevel = .mask, + defaultPrivacyLevel: SessionReplayPrivacyLevel = .mask, customEndpoint: URL? = nil ) { self.replaySampleRate = replaySampleRate diff --git a/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift b/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift index 094f932c7f..2f20a42253 100644 --- a/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift +++ b/DatadogSessionReplay/Tests/Mocks/RecorderMocks.swift @@ -14,16 +14,6 @@ import WebKit @testable import DatadogSessionReplay @testable import TestUtilities -extension PrivacyLevel: AnyMockable, RandomMockable { - public static func mockAny() -> PrivacyLevel { - return .allow - } - - public static func mockRandom() -> PrivacyLevel { - return [.allow, .mask, .maskUserInput].randomElement()! - } -} - // MARK: - ViewTreeSnapshot Mocks extension ViewTreeSnapshot: AnyMockable, RandomMockable { diff --git a/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift b/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift index ebaad783a7..b8ef27f26f 100644 --- a/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift +++ b/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift @@ -5,6 +5,8 @@ */ import Foundation +import DatadogInternal + #if os(tvOS) #warning("Datadog WebView Tracking does not support tvOS") #else @@ -35,7 +37,7 @@ public final class objc_WebViewTracking: NSObject { /// Mask input elements, but record all other content. case maskUserInput - internal var toSwift: WebViewTracking.SessionReplayConfiguration.PrivacyLevel { + internal var toSwift: SessionReplayPrivacyLevel { switch self { case .allow: return .allow case .mask: return .mask diff --git a/DatadogWebViewTracking/Sources/WebViewTracking.swift b/DatadogWebViewTracking/Sources/WebViewTracking.swift index 94751a4d5c..c9e3ae75f4 100644 --- a/DatadogWebViewTracking/Sources/WebViewTracking.swift +++ b/DatadogWebViewTracking/Sources/WebViewTracking.swift @@ -30,27 +30,15 @@ public enum WebViewTracking { /// Setting the Session Replay configuration in `WebViewTracking` will enable transmitting replay data from /// the Datadog Browser SDK installed in the web page. Datadog will then be able to combine the native /// and web recordings in a single replay. - public struct SessionReplayConfiguration { - /// Available privacy levels for content masking. - public enum PrivacyLevel: String { - /// Record all content. - case allow - - /// Mask all content. - case mask - - /// Mask input elements, but record all other content. - case maskUserInput = "mask_user_input" - } - + public struct SessionReplayConfiguration: DatadogInternal.SessionReplayConfiguration { /// The privacy level to use for the web view replay recording. - public var privacyLevel: PrivacyLevel + public var privacyLevel: SessionReplayPrivacyLevel /// Creates Webview Session Replay configuration. /// /// - Parameters: /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. - public init(privacyLevel: PrivacyLevel = .mask) { + public init(privacyLevel: SessionReplayPrivacyLevel = .mask) { self.privacyLevel = privacyLevel } } @@ -140,10 +128,15 @@ public enum WebViewTracking { .map { return "\"\($0)\"" } .joined(separator: ",") - let privacyLevel = sessionReplayConfiguration?.privacyLevel ?? .mask + let sessionReplay = sessionReplayConfiguration ?? core.feature( + named: "session-replay", + type: DatadogInternal.SessionReplayConfiguration.self + ) + + let privacyLevel = sessionReplay?.privacyLevel ?? .mask // Share native capabilities with Browser SDK - let capabilities = sessionReplayConfiguration != nil ? "\"records\"" : "" + let capabilities = sessionReplay != nil ? "\"records\"" : "" let js = """ \(Self.jsCodePrefix) diff --git a/DatadogWebViewTracking/Tests/Mocks.swift b/DatadogWebViewTracking/Tests/Mocks.swift index 36571392dc..5da8546906 100644 --- a/DatadogWebViewTracking/Tests/Mocks.swift +++ b/DatadogWebViewTracking/Tests/Mocks.swift @@ -43,14 +43,4 @@ final class MockScriptMessage: WKScriptMessage { override weak var webView: WKWebView? { _webView } } -extension WebViewTracking.SessionReplayConfiguration.PrivacyLevel: AnyMockable, RandomMockable { - public static func mockAny() -> Self { - .allow - } - - public static func mockRandom() -> Self { - [.allow, .mask, .maskUserInput].randomElement()! - } -} - #endif diff --git a/TestUtilities/Mocks/CoreMocks/FeatureRegistrationCoreMock.swift b/TestUtilities/Mocks/CoreMocks/FeatureRegistrationCoreMock.swift index aa4aa28e69..bd005e47e9 100644 --- a/TestUtilities/Mocks/CoreMocks/FeatureRegistrationCoreMock.swift +++ b/TestUtilities/Mocks/CoreMocks/FeatureRegistrationCoreMock.swift @@ -40,7 +40,7 @@ public class FeatureRegistrationCoreMock: DatadogCoreProtocol { registeredFeatures.append(feature) } - public func get(feature type: T.Type) -> T? where T : DatadogFeature { + public func feature(named name: String, type: T.Type) -> T? { return registeredFeatures.firstElement(of: type) } diff --git a/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift b/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift index 27ffb9b0e7..e944f8fc10 100644 --- a/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift +++ b/TestUtilities/Mocks/CoreMocks/PassthroughCoreMock.swift @@ -85,7 +85,7 @@ open class PassthroughCoreMock: DatadogCoreProtocol, FeatureScope { /// no-op public func register(feature: T) throws where T: DatadogFeature { } /// no-op - public func get(feature type: T.Type) -> T? where T: DatadogFeature { nil } + public func feature(named name: String, type: T.Type) -> T? { nil } /// Always returns a feature-scope. public func scope(for featureType: T.Type) -> FeatureScope where T : DatadogFeature { diff --git a/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift b/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift index 45f87c5e62..d75274cbf4 100644 --- a/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift +++ b/TestUtilities/Mocks/CoreMocks/SingleFeatureCoreMock.swift @@ -89,7 +89,7 @@ public final class SingleFeatureCoreMock: PassthroughCoreMock where Fea self.feature = feature as? Feature } - public override func get(feature type: T.Type) -> T? where T: DatadogFeature { + public override func feature(named name: String, type: T.Type) -> T? { feature as? T } diff --git a/TestUtilities/Mocks/FeatureModels/SessionReplayConfigurationMocks.swift b/TestUtilities/Mocks/FeatureModels/SessionReplayConfigurationMocks.swift new file mode 100644 index 0000000000..f6770493e1 --- /dev/null +++ b/TestUtilities/Mocks/FeatureModels/SessionReplayConfigurationMocks.swift @@ -0,0 +1,18 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal + +extension SessionReplayPrivacyLevel: AnyMockable, RandomMockable { + public static func mockAny() -> Self { + .allow + } + + public static func mockRandom() -> Self { + [.allow, .mask, .maskUserInput].randomElement()! + } +} From 7f3e31a3e9a0db52487b39c07c62cece7937371d Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 4 Jun 2024 14:07:27 +0200 Subject: [PATCH 12/71] RUM-4632 Remove SR config in WVT --- .../Public/WebLogIntegrationTests.swift | 1 - .../RUM/WebEventIntegrationTests.swift | 1 - .../ObjcAPITests/DDWebViewTracking+apiTests.m | 11 +--- .../SessionReplayConfiguration.swift | 6 ++ .../Feature/SessionReplayFeature.swift | 2 - .../Sources/ObjC/WebViewTracking+objc.swift | 65 +------------------ .../Sources/WebViewTracking.swift | 28 +------- .../Tests/WebViewTrackingTests.swift | 19 +++--- .../project.pbxproj | 36 ++++++++++ 9 files changed, 57 insertions(+), 112 deletions(-) diff --git a/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift index 8d14130b91..c4a67441b2 100644 --- a/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/WebLogIntegrationTests.swift @@ -36,7 +36,6 @@ class WebLogIntegrationTests: XCTestCase { hosts: [], hostsSanitizer: HostsSanitizer(), logsSampleRate: 100, - sessionReplayConfiguration: nil, in: core ) } diff --git a/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift b/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift index 34b8fb595c..dff5694e51 100644 --- a/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/WebEventIntegrationTests.swift @@ -35,7 +35,6 @@ class WebEventIntegrationTests: XCTestCase { hosts: [], hostsSanitizer: HostsSanitizer(), logsSampleRate: 100, - sessionReplayConfiguration: nil, in: core ) } diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m index f5082efaaa..d6f908d869 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDWebViewTracking+apiTests.m @@ -32,19 +32,10 @@ - (void)testDDWebViewTrackingAPI { [DDWebViewTracking enableWithWebView:webView hosts:[NSSet setWithArray:@[@"host1.com", @"host2.com"]] logsSampleRate:100.0 - with:nil]; + ]; [DDWebViewTracking disableWithWebView:webView]; } -- (void)testDDWebViewTrackingSessionReplayConfigurationAPI { - DDWebViewTrackingSessionReplayConfiguration *config = [[DDWebViewTrackingSessionReplayConfiguration alloc] init]; - XCTAssertEqual(config.privacyLevel, DDPrivacyLevelMask); - config.privacyLevel = DDPrivacyLevelAllow; - XCTAssertEqual(config.privacyLevel, DDPrivacyLevelAllow); - config.privacyLevel = DDPrivacyLevelMaskUserInput; - XCTAssertEqual(config.privacyLevel, DDPrivacyLevelMaskUserInput); -} - #pragma clang diagnostic pop @end diff --git a/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift b/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift index fc7918f124..1cf6f3814d 100644 --- a/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift +++ b/DatadogInternal/Sources/Models/SessionReplay/SessionReplayConfiguration.swift @@ -6,6 +6,8 @@ import Foundation +public let SessionReplayFeaturneName = "session-replay" + /// Available privacy levels for content masking in Session Replay. public enum SessionReplayPrivacyLevel: String { /// Record all content. @@ -33,3 +35,7 @@ public protocol SessionReplayConfiguration { /// The privacy level to use for the web view replay recording. var privacyLevel: SessionReplayPrivacyLevel { get } } + +extension DatadogFeature where Self: SessionReplayConfiguration { + public static var name: String { SessionReplayFeaturneName } +} diff --git a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift index 6ef82afa22..ab66080437 100644 --- a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift +++ b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift @@ -9,8 +9,6 @@ import Foundation import DatadogInternal internal class SessionReplayFeature: SessionReplayConfiguration, DatadogRemoteFeature { - static let name: String = "session-replay" - let requestBuilder: FeatureRequestBuilder let messageReceiver: FeatureMessageReceiver let performanceOverride: PerformancePresetOverride? diff --git a/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift b/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift index b8ef27f26f..8af8c2374a 100644 --- a/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift +++ b/DatadogWebViewTracking/Sources/ObjC/WebViewTracking+objc.swift @@ -18,64 +18,6 @@ import WebKit public final class objc_WebViewTracking: NSObject { override private init() { } - /// The Session Replay configuration to capture records coming from the web view. - /// - /// Setting the Session Replay configuration in `WebViewTracking` will enable transmitting replay data from - /// the Datadog Browser SDK installed in the web page. Datadog will then be able to combine the native - /// and web recordings in a single replay. - @objc(DDWebViewTrackingSessionReplayConfiguration) - @_spi(objc) - public final class SessionReplayConfiguration: NSObject { - /// Available privacy levels for content masking. - @objc(DDPrivacyLevel) - @_spi(objc) - public enum PrivacyLevel: Int { - /// Record all content. - case allow - /// Mask all content. - case mask - /// Mask input elements, but record all other content. - case maskUserInput - - internal var toSwift: SessionReplayPrivacyLevel { - switch self { - case .allow: return .allow - case .mask: return .mask - case .maskUserInput: return .maskUserInput - } - } - } - - /// The privacy level to use for the web view replay recording. - @objc public var privacyLevel: PrivacyLevel - - /// Creates Webview Session Replay configuration. - /// - /// - Parameters: - /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. - @objc - override public init() { - self.privacyLevel = .mask - } - - /// Creates Webview Session Replay configuration. - /// - /// - Parameters: - /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. - @objc - public init( - privacyLevel: PrivacyLevel - ) { - self.privacyLevel = privacyLevel - } - - internal var toSwift: WebViewTracking.SessionReplayConfiguration { - return .init( - privacyLevel: privacyLevel.toSwift - ) - } - } - /// Enables SDK to correlate Datadog RUM events and Logs from the WebView with native RUM session. /// /// If the content loaded in WebView uses Datadog Browser SDK (`v4.2.0+`) and matches specified @@ -86,20 +28,17 @@ public final class objc_WebViewTracking: NSObject { /// - hosts: A set of hosts instrumented with Browser SDK to capture Datadog events from. /// - logsSampleRate: The sampling rate for logs coming from the WebView. Must be a value between `0` and `100`, /// where 0 means no logs will be sent and 100 means all will be uploaded. Default: `100`. - /// - sessionReplayConfiguration: Session Replay configuration to enable linking Web and Native replays. /// - core: Datadog SDK core to use for tracking. @objc public static func enable( webView: WKWebView, hosts: Set = [], - logsSampleRate: Float = 100, - with configuration: SessionReplayConfiguration? = nil + logsSampleRate: Float = 100 ) { WebViewTracking.enable( webView: webView, hosts: hosts, - logsSampleRate: logsSampleRate, - sessionReplayConfiguration: configuration?.toSwift + logsSampleRate: logsSampleRate ) } diff --git a/DatadogWebViewTracking/Sources/WebViewTracking.swift b/DatadogWebViewTracking/Sources/WebViewTracking.swift index c9e3ae75f4..097cc2f05b 100644 --- a/DatadogWebViewTracking/Sources/WebViewTracking.swift +++ b/DatadogWebViewTracking/Sources/WebViewTracking.swift @@ -25,24 +25,6 @@ import WebKit /// - Support users that have difficulty loading web pages on mobile devices public enum WebViewTracking { #if !os(tvOS) - /// The Session Replay configuration to capture records coming from the web view. - /// - /// Setting the Session Replay configuration in `WebViewTracking` will enable transmitting replay data from - /// the Datadog Browser SDK installed in the web page. Datadog will then be able to combine the native - /// and web recordings in a single replay. - public struct SessionReplayConfiguration: DatadogInternal.SessionReplayConfiguration { - /// The privacy level to use for the web view replay recording. - public var privacyLevel: SessionReplayPrivacyLevel - - /// Creates Webview Session Replay configuration. - /// - /// - Parameters: - /// - privacyLevel: The way sensitive content (e.g. text) should be masked. Default: `.mask`. - public init(privacyLevel: SessionReplayPrivacyLevel = .mask) { - self.privacyLevel = privacyLevel - } - } - /// Enables SDK to correlate Datadog RUM events and Logs from the WebView with native RUM session. /// /// If the content loaded in WebView uses Datadog Browser SDK (`v4.2.0+`) and matches specified @@ -53,13 +35,11 @@ public enum WebViewTracking { /// - hosts: A set of hosts instrumented with Browser SDK to capture Datadog events from. /// - logsSampleRate: The sampling rate for logs coming from the WebView. Must be a value between `0` and `100`, /// where 0 means no logs will be sent and 100 means all will be uploaded. Default: `100`. - /// - sessionReplayConfiguration: Session Replay configuration to enable linking Web and Native replays. /// - core: Datadog SDK core to use for tracking. public static func enable( webView: WKWebView, hosts: Set = [], logsSampleRate: Float = 100, - sessionReplayConfiguration: SessionReplayConfiguration? = nil, in core: DatadogCoreProtocol = CoreRegistry.default ) { enable( @@ -67,7 +47,6 @@ public enum WebViewTracking { hosts: hosts, hostsSanitizer: HostsSanitizer(), logsSampleRate: logsSampleRate, - sessionReplayConfiguration: sessionReplayConfiguration, in: core ) } @@ -95,7 +74,6 @@ public enum WebViewTracking { hosts: Set, hostsSanitizer: HostsSanitizing, logsSampleRate: Float, - sessionReplayConfiguration: SessionReplayConfiguration?, in core: DatadogCoreProtocol ) { let isTracking = controller.userScripts.contains { $0.source.starts(with: Self.jsCodePrefix) } @@ -128,9 +106,9 @@ public enum WebViewTracking { .map { return "\"\($0)\"" } .joined(separator: ",") - let sessionReplay = sessionReplayConfiguration ?? core.feature( - named: "session-replay", - type: DatadogInternal.SessionReplayConfiguration.self + let sessionReplay = core.feature( + named: SessionReplayFeaturneName, + type: SessionReplayConfiguration.self ) let privacyLevel = sessionReplay?.privacyLevel ?? .mask diff --git a/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift b/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift index dfbbdd8a5f..622367be96 100644 --- a/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift +++ b/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift @@ -24,7 +24,6 @@ class WebViewTrackingTests: XCTestCase { hosts: [host], hostsSanitizer: mockSanitizer, logsSampleRate: 30, - sessionReplayConfiguration: nil, in: PassthroughCoreMock() ) @@ -49,11 +48,17 @@ class WebViewTrackingTests: XCTestCase { } func testItAddsUserScriptWithSessionReplay() throws { + struct SessionReplayFeature: DatadogFeature, SessionReplayConfiguration { + static let name = "session-replay" + let messageReceiver: FeatureMessageReceiver = NOPFeatureMessageReceiver() + let privacyLevel: SessionReplayPrivacyLevel + } + let mockSanitizer = HostsSanitizerMock() let controller = DDUserContentController() let host: String = .mockRandom() - let sessionReplayConfiguration = WebViewTracking.SessionReplayConfiguration( + let sr = SessionReplayFeature( privacyLevel: .mockRandom() ) @@ -62,8 +67,7 @@ class WebViewTrackingTests: XCTestCase { hosts: [host], hostsSanitizer: mockSanitizer, logsSampleRate: 30, - sessionReplayConfiguration: sessionReplayConfiguration, - in: PassthroughCoreMock() + in: SingleFeatureCoreMock(feature: sr) ) let script = try XCTUnwrap(controller.userScripts.last) @@ -80,7 +84,7 @@ class WebViewTrackingTests: XCTestCase { return '["records"]' }, getPrivacyLevel() { - return '\(sessionReplayConfiguration.privacyLevel.rawValue)' + return '\(sr.privacyLevel.rawValue)' } } """) @@ -97,7 +101,6 @@ class WebViewTrackingTests: XCTestCase { hosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer, logsSampleRate: 30, - sessionReplayConfiguration: nil, in: PassthroughCoreMock() ) @@ -129,7 +132,6 @@ class WebViewTrackingTests: XCTestCase { hosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer, logsSampleRate: 100, - sessionReplayConfiguration: nil, in: PassthroughCoreMock() ) } @@ -208,7 +210,6 @@ class WebViewTrackingTests: XCTestCase { hosts: ["datadoghq.com"], hostsSanitizer: HostsSanitizerMock(), logsSampleRate: 100, - sessionReplayConfiguration: nil, in: core ) @@ -261,7 +262,6 @@ class WebViewTrackingTests: XCTestCase { hosts: ["datadoghq.com"], hostsSanitizer: HostsSanitizerMock(), logsSampleRate: 100, - sessionReplayConfiguration: nil, in: core ) @@ -354,7 +354,6 @@ class WebViewTrackingTests: XCTestCase { hosts: ["datadoghq.com"], hostsSanitizer: HostsSanitizerMock(), logsSampleRate: 100, - sessionReplayConfiguration: nil, in: core ) diff --git a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj index 9d2c26b486..cca59f821d 100644 --- a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj +++ b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj @@ -826,6 +826,7 @@ 61441C0024616DE9003D8BB8 /* Resources */, D240687A27CF982B00C04F44 /* Embed Frameworks */, 5DAD8F2510CB09A5BA7723B9 /* [CP] Copy Pods Resources */, + 5FC520EDF6AC52CE770441C8 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -848,6 +849,7 @@ 61441C2724616F1D003D8BB8 /* Frameworks */, 61441C2824616F1D003D8BB8 /* Resources */, 7F6DA13A8C70117DE98BAFD7 /* [CP] Copy Pods Resources */, + FF0828D858AD2E214162EA0C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -978,6 +980,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-resources.sh\"\n"; showEnvVarsInLog = 0; }; + 5FC520EDF6AC52CE770441C8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 7F6DA13A8C70117DE98BAFD7 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1017,6 +1036,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + FF0828D858AD2E214162EA0C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ From c669cd1d6c401c275748f93f06be6af39a4b1099 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 4 Jun 2024 15:55:01 +0200 Subject: [PATCH 13/71] RUM-4632 Fix snapshot tests build --- .../SRSnapshotTests/Utils/SnapshotTestCase.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift index 829c935391..e20b816e3c 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/SnapshotTestCase.swift @@ -7,11 +7,12 @@ import XCTest import SRFixtures import TestUtilities +import DatadogInternal @_spi(Internal) @testable import DatadogSessionReplay @testable import SRHost -private var defaultPrivacyLevel: SessionReplay.Configuration.PrivacyLevel { +private var defaultPrivacyLevel: SessionReplayPrivacyLevel { return SessionReplay.Configuration(replaySampleRate: 100).defaultPrivacyLevel } @@ -36,7 +37,7 @@ internal class SnapshotTestCase: XCTestCase { } /// Captures side-by-side snapshot of the app UI and recorded wireframes. - func takeSnapshot(with privacyLevel: SessionReplay.Configuration.PrivacyLevel = defaultPrivacyLevel) throws -> UIImage { + func takeSnapshot(with privacyLevel: SessionReplayPrivacyLevel = defaultPrivacyLevel) throws -> UIImage { let expectWireframes = self.expectation(description: "Wait for wireframes") let expectResources = self.expectation(description: "Wait for resources") @@ -115,8 +116,8 @@ internal class SnapshotTestCase: XCTestCase { } func forPrivacyModes( - _ modes: [SessionReplay.Configuration.PrivacyLevel] = [.mask, .allow, .maskUserInput], - do work: (SessionReplay.Configuration.PrivacyLevel) throws -> Void) rethrows { + _ modes: [SessionReplayPrivacyLevel] = [.mask, .allow, .maskUserInput], + do work: (SessionReplayPrivacyLevel) throws -> Void) rethrows { try modes.forEach { try work($0) } } } From bc65793458ff5b82b2b103204bc698485ce3ada7 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 4 Jun 2024 16:53:40 +0200 Subject: [PATCH 14/71] Fix flaky test --- .../NodeRecorders/UINavigationBarRecorderTests.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift index 1f7ae9b564..857ddb5d5e 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift @@ -14,7 +14,13 @@ class UINavigationBarRecorderTests: XCTestCase { func testWhenViewIsOfExpectedType() throws { // Given - let navigationBar = UINavigationBar.mock(withFixture: .allCases.randomElement()!) + let fixtures: [ViewAttributes.Fixture] = [ + .visible(.noAppearance), + .visible(.someAppearance), + .opaque + ] + + let navigationBar = UINavigationBar.mock(withFixture: fixtures.randomElement()!) let viewAttributes = ViewAttributes(frameInRootView: navigationBar.frame, view: navigationBar) // When From b12c9fbd77818faff0108eede5dda705b9373f17 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 4 Jun 2024 16:55:15 +0200 Subject: [PATCH 15/71] Fix SR test flakiness --- .../NodeRecorders/UINavigationBarRecorderTests.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift index 1f7ae9b564..857ddb5d5e 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift @@ -14,7 +14,13 @@ class UINavigationBarRecorderTests: XCTestCase { func testWhenViewIsOfExpectedType() throws { // Given - let navigationBar = UINavigationBar.mock(withFixture: .allCases.randomElement()!) + let fixtures: [ViewAttributes.Fixture] = [ + .visible(.noAppearance), + .visible(.someAppearance), + .opaque + ] + + let navigationBar = UINavigationBar.mock(withFixture: fixtures.randomElement()!) let viewAttributes = ViewAttributes(frameInRootView: navigationBar.frame, view: navigationBar) // When From 57f8b8061bac132f65b8969bb99df63278a9ad15 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 4 Jun 2024 16:02:04 +0200 Subject: [PATCH 16/71] chore: Disable flaky `testSetActive_givenParentSpan()` OTelSpan test --- .../xcschemes/DatadogCore iOS.xcscheme | 3 + .../Tests/OpenTelemetry/OTelSpanTests.swift | 187 ++++++------------ DatadogTrace/Tests/TracingFeatureMocks.swift | 22 +++ 3 files changed, 81 insertions(+), 131 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme index 20bcde93a6..d2e0f0741f 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore iOS.xcscheme @@ -230,6 +230,9 @@ + + DatadogTracer { + return DatadogTracer( + featureScope: featureScope, + localTraceSampler: localTraceSampler, + tags: tags, + traceIDGenerator: traceIDGenerator, + spanIDGenerator: spanIDGenerator, + dateProvider: dateProvider, + loggingIntegration: loggingIntegration, + spanEventBuilder: spanEventBuilder + ) + } } extension TracingWithLoggingIntegration { From dda8c0f37e83b5bc12d5393a446474b835c2d24b Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Tue, 4 Jun 2024 10:23:48 +0200 Subject: [PATCH 17/71] [SR] Add background color support for TabBar --- .../ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift index 14a7903d80..a2cbfa5295 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift @@ -36,6 +36,10 @@ internal struct UITabBarRecorder: NodeRecorder { private func inferColor(of tabBar: UITabBar) -> CGColor { // TODO: RUMM-2791 Enhance appearance of `UITabBar` and `UINavigationBar` in SR + if let color = tabBar.backgroundColor { + return color.cgColor + } + if #available(iOS 13.0, *) { switch UITraitCollection.current.userInterfaceStyle { case .light: From 3200a0c4eb99d972b5ead2fa00ac8d2d39cb6992 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 5 Jun 2024 16:29:50 +0200 Subject: [PATCH 18/71] chore: Disable flaky `testSetActive_givenParentSpan()` OTelSpan test for tvOS --- .../xcshareddata/xcschemes/DatadogCore tvOS.xcscheme | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme index b30d9a0578..21868a8015 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogCore tvOS.xcscheme @@ -212,6 +212,11 @@ BlueprintName = "DatadogTraceTests tvOS" ReferencedContainer = "container:Datadog.xcodeproj"> + + + + From c65a21f3a0f7cf85eb51a83e678ad1a92cae1ab4 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Fri, 7 Jun 2024 13:56:46 +0200 Subject: [PATCH 19/71] Add connect, trace, options values to DDRumMethod type --- CHANGELOG.md | 5 ++++- DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift | 3 +++ .../Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m | 3 ++- DatadogObjc/Sources/RUM/RUM+objc.swift | 6 ++++++ api-surface-objc | 3 +++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 052ae4f300..c8ebc804b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [IMPROVEMENT] Add `.connect`, `.trace`, `.options` values to `DDRUMMethod` type. See [#1886][] + # 2.12.0 / 03-06-2024 - [IMPROVEMENT] Crash errors now include up-to-date global RUM attributes. See [#1834][] @@ -675,6 +677,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1854]: https://github.com/DataDog/dd-sdk-ios/pull/1854 [#1828]: https://github.com/DataDog/dd-sdk-ios/pull/1828 [#1835]: https://github.com/DataDog/dd-sdk-ios/pull/1835 +[#1886]: https://github.com/DataDog/dd-sdk-ios/pull/1886 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu @@ -704,4 +707,4 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [@dfed]: https://github.com/dfed [@cltnschlosser]: https://github.com/cltnschlosser [@alexfanatics]: https://github.com/alexfanatics -[@changm4n]: https://github.com/changm4n \ No newline at end of file +[@changm4n]: https://github.com/changm4n diff --git a/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift b/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift index e2806786cf..89c9f85c11 100644 --- a/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift +++ b/DatadogCore/Tests/DatadogObjc/DDRUMMonitorTests.swift @@ -129,6 +129,9 @@ class DDRUMMethodTests: XCTestCase { XCTAssertEqual(DDRUMMethod.put.swiftType, .put) XCTAssertEqual(DDRUMMethod.delete.swiftType, .delete) XCTAssertEqual(DDRUMMethod.patch.swiftType, .patch) + XCTAssertEqual(DDRUMMethod.connect.swiftType, .connect) + XCTAssertEqual(DDRUMMethod.trace.swiftType, .trace) + XCTAssertEqual(DDRUMMethod.options.swiftType, .options) } } diff --git a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m index d603998a99..881f9781ba 100644 --- a/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m +++ b/DatadogCore/Tests/DatadogObjc/ObjcAPITests/DDRUMMonitor+apiTests.m @@ -45,7 +45,8 @@ - (void)testDDRUMResourceTypeAPI { } - (void)testDDRUMMethodAPI { - DDRUMMethodPost; DDRUMMethodGet; DDRUMMethodHead; DDRUMMethodPut; DDRUMMethodDelete; DDRUMMethodPatch; + DDRUMMethodPost; DDRUMMethodGet; DDRUMMethodHead; DDRUMMethodPut; DDRUMMethodDelete; DDRUMMethodPatch; DDRUMMethodConnect; + DDRUMMethodTrace; DDRUMMethodOptions; } - (void)testDDRUMMonitorAPI { diff --git a/DatadogObjc/Sources/RUM/RUM+objc.swift b/DatadogObjc/Sources/RUM/RUM+objc.swift index aec38aceb7..6f2e8cd113 100644 --- a/DatadogObjc/Sources/RUM/RUM+objc.swift +++ b/DatadogObjc/Sources/RUM/RUM+objc.swift @@ -228,6 +228,9 @@ public enum DDRUMMethod: Int { case put case delete case patch + case connect + case trace + case options internal var swiftType: RUMMethod { switch self { @@ -237,6 +240,9 @@ public enum DDRUMMethod: Int { case .put: return .put case .delete: return .delete case .patch: return .patch + case .connect: return .connect + case .trace: return .trace + case .options: return .options default: return .get } } diff --git a/api-surface-objc b/api-surface-objc index 941e1e5609..55f165c08a 100644 --- a/api-surface-objc +++ b/api-surface-objc @@ -206,6 +206,9 @@ public enum DDRUMMethod: Int case put case delete case patch + case connect + case trace + case options public enum DDRUMVitalsFrequency: Int case frequent case average From 2f559baaf601fe9a577190408fee942b6c808920 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 7 Jun 2024 15:16:06 +0200 Subject: [PATCH 20/71] RUM-2814 Add scenario to RUM attribute --- .../SessionReplayWebView/SessionReplayWebViewScenario.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift index cb681c4dc9..d43e01e37e 100644 --- a/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift +++ b/E2ETests/Runner/Scenarios/SessionReplayWebView/SessionReplayWebViewScenario.swift @@ -33,6 +33,8 @@ struct SessionReplayWebViewScenario: Scenario { ) ) + RUMMonitor.shared().addAttribute(forKey: "scenario", value: "SessionReplayWebView") + let storyboard = UIStoryboard(name: "SessionReplayWebView", bundle: nil) return storyboard.instantiateInitialViewController()! } From 26e31526178097ed414a19ca037ac4c730aae076 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 10 Jun 2024 12:18:57 +0200 Subject: [PATCH 21/71] Add snapshot tests --- .../Sources/SRFixtures/Fixtures.swift | 10 + .../Resources/Storyboards/Tabbars.storyboard | 334 ++++++++++++++++++ .../ViewControllers/TabbarControllers.swift | 32 ++ .../SRHost/MenuViewController.swift | 10 + .../SRSnapshotTests/SRSnapshotTests.swift | 43 ++- .../NodeRecorders/UITabBarRecorder.swift | 2 - 6 files changed, 428 insertions(+), 3 deletions(-) create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/Tabbars.storyboard create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift index decdaebf42..a2b7ead57d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift @@ -42,6 +42,9 @@ public enum Fixture: CaseIterable { case navigationBarDefaultNonTranslucentBarTint case navigationBarDefaultTranslucentBackground case navigationBarDefaultNonTranslucentBackground + case tabbar + case embeddedTabbar + case embeddedTabbarUnselectedTintColor public func instantiateViewController() -> UIViewController { switch self { @@ -108,6 +111,12 @@ public enum Fixture: CaseIterable { return vc case .navigationBarDefaultNonTranslucentBackground: return UIStoryboard.navigationBars.instantiateViewController(withIdentifier: "Default_Non_Translucent_Background_Navbar") + case .tabbar: + return UIStoryboard.tabbars.instantiateViewController(withIdentifier: "Tabbars") + case .embeddedTabbar: + return UIStoryboard.tabbars.instantiateViewController(withIdentifier: "EmbeddedTabbar") + case .embeddedTabbarUnselectedTintColor: + return UIStoryboard.tabbars.instantiateViewController(withIdentifier: "EmbeddedTabbarUnselectedTintColor") } } } @@ -119,4 +128,5 @@ internal extension UIStoryboard { static var images: UIStoryboard { UIStoryboard(name: "Images", bundle: .module) } static var unsupportedViews: UIStoryboard { UIStoryboard(name: "UnsupportedViews", bundle: .module) } static var navigationBars: UIStoryboard { UIStoryboard(name: "NavigationBars", bundle: .module) } + static var tabbars: UIStoryboard { UIStoryboard(name: "Tabbars", bundle: .module) } } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/Tabbars.storyboard b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/Tabbars.storyboard new file mode 100644 index 0000000000..3ae05572e0 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/Tabbars.storyboard @@ -0,0 +1,334 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift new file mode 100644 index 0000000000..caaade9074 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift @@ -0,0 +1,32 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import UIKit + +public class TabbarViewControllers: UIViewController { + + @IBOutlet var tabbars: [UITabBar]! + + public override func viewDidLoad() { + for tabbar in tabbars { + // Select the first tabbar's item + // so we can see both a selected and unselected item + tabbar.selectedItem = tabbar.items?[0] + } + } +} + +public class EmbeddedTabbarController: UITabBarController { + public override func viewDidLoad() { + tabBar.unselectedItemTintColor = nil + } +} + +public class EmbeddedTabbarUnselectedTintColorController: UITabBarController { + public override func viewDidLoad() { + tabBar.unselectedItemTintColor = UIColor.green + } +} diff --git a/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift b/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift index ee9ab26870..9b2b947dff 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift @@ -64,8 +64,18 @@ internal extension Fixture { return "Embedded Navigation Bar Default + Translucent + Background color" case .navigationBarDefaultNonTranslucentBackground: return "Embedded Navigation Bar Default + Non Translucent + Background color" + case .tabbar: + return "Tab Bars" + case .embeddedTabbar: + return "Embedded Tab Bar" + case .embeddedTabbarUnselectedTintColor: + return "Embedded Tab Bar Unselected Tint Color" } } + + var slug: String { + self.menuItemTitle.lowercased().replacingOccurrences(of: " ", with: "") + } } internal class MenuViewController: UITableViewController { diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift index 4efb9f5b0a..f70310ffbe 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift @@ -296,7 +296,7 @@ final class SRSnapshotTests: SnapshotTestCase { try forPrivacyModes([.allow, .mask]) { privacyMode in let image = try takeSnapshot(with: privacyMode) - let fileNamePrefix = fixture.menuItemTitle.lowercased().replacingOccurrences(of: " ", with: "") + let fileNamePrefix = fixture.slug DDAssertSnapshotTest( newImage: image, snapshotLocation: .folder(named: snapshotsFolderPath, fileNameSuffix: "-\(fileNamePrefix)-\(privacyMode)-privacy"), @@ -306,4 +306,45 @@ final class SRSnapshotTests: SnapshotTestCase { } } + func testTabBars() throws { + + // - Static Tab Bars + show(fixture: .tabbar) + + try forPrivacyModes([.allow, .mask]) { privacyMode in + let image = try takeSnapshot(with: privacyMode) + DDAssertSnapshotTest( + newImage: image, + snapshotLocation: .folder(named: snapshotsFolderPath, fileNameSuffix: "-\(privacyMode)-privacy"), + record: recordingMode + ) + } + + // - Embedded Tab Bar + show(fixture: .embeddedTabbar) + + try forPrivacyModes([.allow, .mask]) { privacyMode in + let image = try takeSnapshot(with: privacyMode) + let fileNamePrefix = Fixture.embeddedTabbar.slug + DDAssertSnapshotTest( + newImage: image, + snapshotLocation: .folder(named: snapshotsFolderPath, fileNameSuffix: "-\(fileNamePrefix)-\(privacyMode)-privacy"), + record: recordingMode + ) + } + + // - Embedded Tab Bar, with unselected item tint color + show(fixture: .embeddedTabbarUnselectedTintColor) + + try forPrivacyModes([.allow, .mask]) { privacyMode in + let image = try takeSnapshot(with: privacyMode) + let fileNamePrefix = Fixture.embeddedTabbarUnselectedTintColor.slug + DDAssertSnapshotTest( + newImage: image, + snapshotLocation: .folder(named: snapshotsFolderPath, fileNameSuffix: "-\(fileNamePrefix)-\(privacyMode)-privacy"), + record: recordingMode + ) + } + } + } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift index a2cbfa5295..1d49a33ba6 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift @@ -25,7 +25,6 @@ internal struct UITabBarRecorder: NodeRecorder { } private func inferOccupiedFrame(of tabBar: UITabBar, in context: ViewTreeRecordingContext) -> CGRect { - // TODO: RUMM-2791 Enhance appearance of `UITabBar` and `UINavigationBar` in SR var occupiedFrame = tabBar.frame for subview in tabBar.subviews { let subviewFrame = subview.convert(subview.bounds, to: context.coordinateSpace) @@ -35,7 +34,6 @@ internal struct UITabBarRecorder: NodeRecorder { } private func inferColor(of tabBar: UITabBar) -> CGColor { - // TODO: RUMM-2791 Enhance appearance of `UITabBar` and `UINavigationBar` in SR if let color = tabBar.backgroundColor { return color.cgColor } From 690aaa663aa70b954f9a56430fc246ae471a2e12 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Mon, 10 Jun 2024 12:23:05 +0200 Subject: [PATCH 22/71] Add new snapshots for Tab Bars --- .../_snapshots_/pointers/testTabBars()-allow-privacy.png.json | 1 + .../pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json | 1 + .../pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json | 1 + ...rs()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json | 1 + ...ars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json | 1 + .../_snapshots_/pointers/testTabBars()-mask-privacy.png.json | 1 + 6 files changed, 6 insertions(+) create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json create mode 100644 DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json new file mode 100644 index 0000000000..67f186dd17 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"c8db9a89ac040c3733ec239bf92a2bfd6253dee4"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json new file mode 100644 index 0000000000..ee875c9a23 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"72d50780fb4df2e058dad0e8953e439097fd3eaa"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json new file mode 100644 index 0000000000..14834eb0ce --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"a15b968196c3fd0a268bd3e82a37fef10b31009f"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json new file mode 100644 index 0000000000..7705782f2e --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"a0cf5709cf5cb508728fd1d2ad74f6afd2153a27"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json new file mode 100644 index 0000000000..50a1a552f5 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"dccf7efc3a8379f8cf0d1ee4d63e00a30206bc04"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json new file mode 100644 index 0000000000..5028df5782 --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"ace6b7b1f9db81e24d7d19d3fa3652e7b6f290dc"} \ No newline at end of file From 6e0872aa35f620c8f78281162b05c5308aab8656 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 11 Jun 2024 10:45:56 +0200 Subject: [PATCH 23/71] RUM-2442 emit _dd.session.id and _dd.application.id on logs with view.id --- .../Sources/RUMMonitor/Scopes/RUMApplicationScope.swift | 5 ----- DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift | 5 ----- DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift | 2 ++ 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index 65e6dab356..5279ad4711 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -43,11 +43,6 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { activeViewName: nil, activeUserActionID: nil ) - - // Notify Synthetics if needed - if dependencies.syntheticsTest != nil { - NSLog("_dd.application.id=" + dependencies.rumApplicationID) - } } // MARK: - RUMContextProvider diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index 5154f12e38..e46c343ff4 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -119,11 +119,6 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // Update fatal error context with recent RUM session state: dependencies.fatalErrorContext.sessionState = state - - // Notify Synthetics if needed - if dependencies.syntheticsTest != nil && sessionUUID != .nullUUID { - NSLog("_dd.session.id=" + sessionUUID.toRUMDataFormat) - } } /// Creates a new Session upon expiration of the previous one. diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 031fa23332..bca5bb5ca4 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -127,6 +127,8 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // Notify Synthetics if needed if dependencies.syntheticsTest != nil && self.context.sessionID != .nullUUID { + NSLog("_dd.session.id=" + self.context.sessionID.toRUMDataFormat) + NSLog("_dd.application.id=" + self.context.rumApplicationID) NSLog("_dd.view.id=" + self.viewUUID.toRUMDataFormat) } } From 5778c80835b633f8ce7a49c3c29602ba79cf2a56 Mon Sep 17 00:00:00 2001 From: Marie Denis Date: Tue, 11 Jun 2024 11:53:10 +0200 Subject: [PATCH 24/71] RUM-4151 Address nit --- .../Sources/SRFixtures/ViewControllers/TabbarControllers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift index caaade9074..103ac32b29 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/TabbarControllers.swift @@ -14,7 +14,7 @@ public class TabbarViewControllers: UIViewController { for tabbar in tabbars { // Select the first tabbar's item // so we can see both a selected and unselected item - tabbar.selectedItem = tabbar.items?[0] + tabbar.selectedItem = tabbar.items?.first } } } From 855249a4905eab96acd243a3af56b6515eafe6a5 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 10:07:12 +0200 Subject: [PATCH 25/71] RUM-4829 Bump `IPHONEOS_DEPLOYMENT_TARGET` for Xcode project also, move all `DEPLOYMENT_TARGET` settings to single place: `Base.xcconfig` --- Datadog/Datadog.xcodeproj/project.pbxproj | 12 +++--------- Makefile | 9 --------- xcconfigs/Base.xcconfig | 5 +++++ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index dba48a02b1..f6ab1261db 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -10055,6 +10055,7 @@ }; 61133B94242393DE00786299 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 61569894256D0E9A00C6AADA /* Base.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -10103,14 +10104,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -10118,6 +10117,7 @@ }; 61133B95242393DE00786299 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 61569894256D0E9A00C6AADA /* Base.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -10160,13 +10160,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -10992,6 +10990,7 @@ }; 9E2FB28224476765001C9B7B /* Integration */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 61569894256D0E9A00C6AADA /* Base.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -11035,13 +11034,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -11766,7 +11763,6 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.TestUtilities; PRODUCT_NAME = TestUtilities; @@ -11805,7 +11801,6 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.TestUtilities; PRODUCT_NAME = TestUtilities; @@ -11844,7 +11839,6 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.TestUtilities; PRODUCT_NAME = TestUtilities; diff --git a/Makefile b/Makefile index 4852b8ad31..f2b2243240 100644 --- a/Makefile +++ b/Makefile @@ -18,12 +18,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) DD_SDK_ENABLE_EXPERIMENTAL_AP // To build only active architecture for all configurations. This gives us ~10% build time gain\n // in targets which do not use 'Debug' configuration.\n ONLY_ACTIVE_ARCH = YES\n -\n -// Adjust the deployment target for all projects and targets in `dd-sdk-ios` (including Datadog.xcworkspace and IntegrationTests.xcworkspace).\n -// This is to fix Xcode 15 warnings and errors like:\n -// - 'The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 11.0, but the range of supported deployment target versions is 12.0 to 17.0.99.'.\n -// - 'Compiling for iOS 11.0, but module 'SRFixtures' has a minimum deployment target of iOS 12.0'\n -IPHONEOS_DEPLOYMENT_TARGET=12.0\n endef export DD_SDK_BASE_XCCONFIG @@ -33,9 +27,6 @@ SWIFT_TREAT_WARNINGS_AS_ERRORS = YES\n \n // If running on CI. This value is injected to some targets through their `Info.plist`:\n IS_CI = true\n -\n -// Use iOS 11 deployment target on CI as long as we use Xcode 14.x for integration\n -IPHONEOS_DEPLOYMENT_TARGET=11.0\n endef export DD_SDK_BASE_XCCONFIG_CI diff --git a/xcconfigs/Base.xcconfig b/xcconfigs/Base.xcconfig index f958e75894..68a709e7a4 100644 --- a/xcconfigs/Base.xcconfig +++ b/xcconfigs/Base.xcconfig @@ -7,5 +7,10 @@ DD_CR_SDK_PRODUCT_NAME=DatadogCrashReporting ARCHS[sdk=iphoneos*]=$(ARCHS_STANDARD) arm64e +// Minimum deployment targets for building the SDK (including Carthage build from sources) +IPHONEOS_DEPLOYMENT_TARGET=12.0 +TVOS_DEPLOYMENT_TARGET=11.0 +MACOSX_DEPLOYMENT_TARGET=12.6 + // Include internal base config (git-ignored, so excluded from Carthage build) #include? "Base.local.xcconfig" From d8ba6df829d63317f34c12982af2d92fdf15e3f1 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 10:09:34 +0200 Subject: [PATCH 26/71] RUM-4829 Bump `IPHONEOS_DEPLOYMENT_TARGET` in podspecs --- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogInternal.podspec | 2 +- DatadogLogs.podspec | 2 +- DatadogObjc.podspec | 2 +- DatadogRUM.podspec | 2 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- DatadogTrace.podspec | 2 +- DatadogWebViewTracking.podspec | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index 26bd6e307b..373d5e6dae 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogCore.podspec b/DatadogCore.podspec index db695ade56..9f0a76ff14 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index 80d2677cce..d715a3b295 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => 'https://github.com/DataDog/dd-sdk-ios.git', :tag => s.version.to_s } diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index 8bf055e353..db33ab60a4 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index ece57e14c4..06078efc19 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index 83342fd77b..f849a7d198 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => 'https://github.com/DataDog/dd-sdk-ios.git', :tag => s.version.to_s } diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index f564323599..f809da8ffe 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index 4d33683889..bd1b292570 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index dc74781193..eb98a0f6dc 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| s.deprecated_in_favor_of = "DatadogAlamofireExtension" s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index e3fd72abd6..8728195fca 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.deprecated_in_favor_of = 'DatadogCrashReporting' diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 980f790bfa..98f69f5cdf 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.deprecated_in_favor_of = 'DatadogObjc' diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index 92c1cc5593..43b9592963 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index ed7e11024f..8116e118c2 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogWebViewTracking.podspec b/DatadogWebViewTracking.podspec index 4db4326cb6..e5a8843855 100644 --- a/DatadogWebViewTracking.podspec +++ b/DatadogWebViewTracking.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } From 48a58cda7bcba59921fe9d8c22081220a89e891a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 10:10:49 +0200 Subject: [PATCH 27/71] RUM-4829 Bump `IPHONEOS_DEPLOYMENT_TARGET` for SPM --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 17943de813..8d7125e24f 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import Foundation let package = Package( name: "Datadog", platforms: [ - .iOS(.v11), + .iOS(.v12), .tvOS(.v11), .macOS(.v12) ], From a57a08bfb0f2dbf74b0acf97c9ed266f6784f663 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 10:30:57 +0200 Subject: [PATCH 28/71] RUM-4829 Bump iOS deployment target in IntegrationTests as 11.0 is no longer supported by the SDK --- .../project.pbxproj | 51 +++++++++---------- IntegrationTests/Podfile | 2 +- IntegrationTests/xcconfigs/Base.xcconfig | 4 ++ TestUtilities.podspec | 2 +- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj index cca59f821d..37dd4ac5f6 100644 --- a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj +++ b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -826,7 +826,7 @@ 61441C0024616DE9003D8BB8 /* Resources */, D240687A27CF982B00C04F44 /* Embed Frameworks */, 5DAD8F2510CB09A5BA7723B9 /* [CP] Copy Pods Resources */, - 5FC520EDF6AC52CE770441C8 /* [CP] Embed Pods Frameworks */, + DF3E2FB0EA50CD3C8F9BF10E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -849,7 +849,7 @@ 61441C2724616F1D003D8BB8 /* Frameworks */, 61441C2824616F1D003D8BB8 /* Resources */, 7F6DA13A8C70117DE98BAFD7 /* [CP] Copy Pods Resources */, - FF0828D858AD2E214162EA0C /* [CP] Embed Pods Frameworks */, + D9453E03D25A266FEA724F80 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -980,23 +980,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 5FC520EDF6AC52CE770441C8 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 7F6DA13A8C70117DE98BAFD7 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1036,7 +1019,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - FF0828D858AD2E214162EA0C /* [CP] Embed Pods Frameworks */ = { + D9453E03D25A266FEA724F80 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1053,6 +1036,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS-IntegrationScenarios/Pods-Runner iOS-IntegrationScenarios-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + DF3E2FB0EA50CD3C8F9BF10E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner iOS/Pods-Runner iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1180,6 +1180,7 @@ /* Begin XCBuildConfiguration section */ 61133B94242393DE00786299 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D223CA4E29966B18000CEDBF /* Base.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1228,14 +1229,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TVOS_DEPLOYMENT_TARGET = 11.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1243,6 +1242,7 @@ }; 61133B95242393DE00786299 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D223CA4E29966B18000CEDBF /* Base.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1285,13 +1285,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1430,6 +1428,7 @@ }; 9E2FB28224476765001C9B7B /* Integration */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D223CA4E29966B18000CEDBF /* Base.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1473,13 +1472,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - TVOS_DEPLOYMENT_TARGET = 11.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; diff --git a/IntegrationTests/Podfile b/IntegrationTests/Podfile index d7f2b1cb87..7936319e30 100644 --- a/IntegrationTests/Podfile +++ b/IntegrationTests/Podfile @@ -1,5 +1,5 @@ target 'Runner iOS' do - platform :ios, '11.0' + platform :ios, '12.0' pod 'DatadogCore', :path => '..' pod 'DatadogLogs', :path => '..' diff --git a/IntegrationTests/xcconfigs/Base.xcconfig b/IntegrationTests/xcconfigs/Base.xcconfig index 88c1960e02..1ac835b13c 100644 --- a/IntegrationTests/xcconfigs/Base.xcconfig +++ b/IntegrationTests/xcconfigs/Base.xcconfig @@ -1,3 +1,7 @@ +// Minimum iOS deployment targets for building integration tests: +IPHONEOS_DEPLOYMENT_TARGET=12.0 +TVOS_DEPLOYMENT_TARGET=11.0 + // Add common settings from Datadog.xcconfig #include "../xcconfigs/Datadog.xcconfig" diff --git a/TestUtilities.podspec b/TestUtilities.podspec index 59a059083a..70f81eb5e1 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.7.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.tvos.deployment_target = '11.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } From 55bef02b958838c6ac859100dfe1ac5033590d7a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 12:49:01 +0200 Subject: [PATCH 29/71] RUM-4829 Remove code specific to iOS 11 --- .../Core/Context/CarrierInfoPublisher.swift | 41 ------------ DatadogCore/Sources/Core/DatadogCore.swift | 6 +- .../Context/CarrierInfoPublisherTests.swift | 67 +++---------------- 3 files changed, 10 insertions(+), 104 deletions(-) diff --git a/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift b/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift index c0e18ba95b..3369ae2f6e 100644 --- a/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift +++ b/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift @@ -11,11 +11,7 @@ import DatadogInternal import CoreTelephony -// MARK: - iOS 12+ - -/// Carrier info provider for iOS 12 and above. /// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` only when `CTCarrier` has changed (e.g. when the SIM card was swapped). -@available(iOS 12, *) internal struct iOS12CarrierInfoPublisher: ContextValuePublisher { let initialValue: CarrierInfo? @@ -44,7 +40,6 @@ internal struct iOS12CarrierInfoPublisher: ContextValuePublisher { } extension CarrierInfo { - @available(iOS 12, *) init?(_ info: CTTelephonyNetworkInfo, service key: String?) { guard let key = key, let radioTechnology = info.serviceCurrentRadioAccessTechnology?[key], @@ -62,42 +57,6 @@ extension CarrierInfo { } } -// MARK: - iOS 11 - -/// Carrier info provider for iOS 11. -/// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` each time. -@available(iOS, deprecated: 12) -internal struct iOS11CarrierInfoReader: ContextValueReader { - private let networkInfo: CTTelephonyNetworkInfo - - init(networkInfo: CTTelephonyNetworkInfo = .init()) { - self.networkInfo = networkInfo - } - - func read(to receiver: inout CarrierInfo?) { - receiver = CarrierInfo(networkInfo) - } -} - -extension CarrierInfo { - @available(iOS, deprecated: 12) - init?(_ info: CTTelephonyNetworkInfo) { - guard - let radioTechnology = info.currentRadioAccessTechnology, - let carrier = info.subscriberCellularProvider - else { - return nil // the service is not registered on any network - } - - self.init( - carrierName: carrier.carrierName, - carrierISOCountryCode: carrier.isoCountryCode, - carrierAllowsVOIP: carrier.allowsVOIP, - radioAccessTechnology: .init(radioTechnology) - ) - } -} - extension CarrierInfo.RadioAccessTechnology { init(_ radioAccessTechnology: String) { switch radioAccessTechnology { diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index a65d0b89cd..579c0cfb64 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -434,11 +434,7 @@ extension DatadogContextProvider { assign(reader: SCNetworkReachabilityReader(), to: \.networkConnectionInfo) } #if os(iOS) && !targetEnvironment(macCatalyst) && !(swift(>=5.9) && os(visionOS)) - if #available(iOS 12, *) { - subscribe(\.carrierInfo, to: iOS12CarrierInfoPublisher()) - } else { - assign(reader: iOS11CarrierInfoReader(), to: \.carrierInfo) - } + subscribe(\.carrierInfo, to: iOS12CarrierInfoPublisher()) #endif #if os(iOS) && !targetEnvironment(simulator) diff --git a/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift index bb18671590..eca548340f 100644 --- a/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift @@ -23,74 +23,25 @@ class CarrierInfoPublisherTests: XCTestCase { serviceSubscriberCellularProviders: [:] ) - func testGivenCellularServiceAvailableOnIOS12AndAbove_itProvidesInitialValue() { - if #available(iOS 12, *) { - // Given - let publisher = iOS12CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) - - // Then - XCTAssertEqual(publisher.initialValue?.carrierName, "Carrier") - XCTAssertEqual(publisher.initialValue?.carrierISOCountryCode, "US") - XCTAssertEqual(publisher.initialValue?.carrierAllowsVOIP, true) - } - } - - func testGivenCellularServiceUnAvailableOnIOS12AndAbove_itProvidesNoInitialValue() { - if #available(iOS 12, *) { - // Given - let publisher = iOS12CarrierInfoPublisher(networkInfo: unavailableCTTelephonyNetworkInfo) - - // Then - XCTAssertNil(publisher.initialValue) - } - } - - @available(iOS, deprecated: 12) - func testGivenCellularServiceUnavailableOnIOS11_whenReadingCurrentCarrierInfo_itReturnsNoValue() { + func testGivenCellularServiceAvailable_itProvidesInitialValue() { // Given - let reader = iOS11CarrierInfoReader(networkInfo: unavailableCTTelephonyNetworkInfo) - - // When - var info: CarrierInfo? = nil - reader.read(to: &info) + let publisher = iOS12CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) // Then - XCTAssertNil(info) + XCTAssertEqual(publisher.initialValue?.carrierName, "Carrier") + XCTAssertEqual(publisher.initialValue?.carrierISOCountryCode, "US") + XCTAssertEqual(publisher.initialValue?.carrierAllowsVOIP, true) } - @available(iOS, deprecated: 12) - func testGivenSubscribediOS11CarrierInfoProvider_whenCarrierInfoChanges_itReadsNewValue() throws { + func testGivenCellularServiceUnAvailable_itProvidesNoInitialValue() { // Given - let reader = iOS11CarrierInfoReader(networkInfo: availableCTTelephonyNetworkInfo) - - let newCarrierName: String = .mockRandom() - let newISOCountryCode: String = .mockRandom() - let newAllowsVOIP: Bool = .mockRandom() - let newRadioAccessTechnology: String = [CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyEdge].randomElement()! - - // When - availableCTTelephonyNetworkInfo.changeCarrier( - newCarrierName: newCarrierName, - newISOCountryCode: newISOCountryCode, - newAllowsVOIP: newAllowsVOIP, - newRadioAccessTechnology: newRadioAccessTechnology - ) - - var info: CarrierInfo? = nil - reader.read(to: &info) + let publisher = iOS12CarrierInfoPublisher(networkInfo: unavailableCTTelephonyNetworkInfo) // Then - XCTAssertEqual(info?.carrierName, newCarrierName) - XCTAssertEqual(info?.carrierISOCountryCode, newISOCountryCode) - XCTAssertEqual(info?.carrierAllowsVOIP, newAllowsVOIP) - XCTAssertEqual(info?.radioAccessTechnology, .init(newRadioAccessTechnology)) + XCTAssertNil(publisher.initialValue) } - func testGivenSubscribediOS12CarrierInfoProvider_whenCarrierInfoChanges_itNotifiesSubscriber() throws { - guard #available(iOS 12, *) else { - return - } - + func testGivenSubscribedInfoProvider_whenCarrierInfoChanges_itNotifiesSubscriber() throws { let expectation = expectation(description: "Notify `CarrierInfo` change") var info: CarrierInfo? = nil let publisher = iOS12CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) From 3409e9d59014e70838a194fb844175fd072f738a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 13:54:47 +0200 Subject: [PATCH 30/71] RUM-4829 Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 052ae4f300..c999bce385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [IMPROVEMENT] Bump `IPHONEOS_DEPLOYMENT_TARGET` from 11 to 12. See [#1891][] + # 2.12.0 / 03-06-2024 - [IMPROVEMENT] Crash errors now include up-to-date global RUM attributes. See [#1834][] @@ -666,6 +668,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1767]: https://github.com/DataDog/dd-sdk-ios/pull/1767 [#1843]: https://github.com/DataDog/dd-sdk-ios/pull/1843 [#1798]: https://github.com/DataDog/dd-sdk-ios/pull/1798 +[#1891]: https://github.com/DataDog/dd-sdk-ios/pull/1891 [#1776]: https://github.com/DataDog/dd-sdk-ios/pull/1776 [#1834]: https://github.com/DataDog/dd-sdk-ios/pull/1834 [#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721 From 3bbefaecd8cd3219b4801e43f56d5ca29fdc9e8e Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 15:46:03 +0200 Subject: [PATCH 31/71] RUM-4829 Bump `TVOS_DEPLOYMENT_TARGET` for Xcode project --- xcconfigs/Base.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcconfigs/Base.xcconfig b/xcconfigs/Base.xcconfig index 68a709e7a4..fd544e20bc 100644 --- a/xcconfigs/Base.xcconfig +++ b/xcconfigs/Base.xcconfig @@ -9,7 +9,7 @@ ARCHS[sdk=iphoneos*]=$(ARCHS_STANDARD) arm64e // Minimum deployment targets for building the SDK (including Carthage build from sources) IPHONEOS_DEPLOYMENT_TARGET=12.0 -TVOS_DEPLOYMENT_TARGET=11.0 +TVOS_DEPLOYMENT_TARGET=12.0 MACOSX_DEPLOYMENT_TARGET=12.6 // Include internal base config (git-ignored, so excluded from Carthage build) From bc3953c28cd2cd22979a259a807ae9b13d5cef9d Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 15:47:47 +0200 Subject: [PATCH 32/71] RUM-4829 Bump `TVOS_DEPLOYMENT_TARGET` in podspecs --- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogInternal.podspec | 2 +- DatadogLogs.podspec | 2 +- DatadogObjc.podspec | 2 +- DatadogRUM.podspec | 2 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- DatadogTrace.podspec | 2 +- TestUtilities.podspec | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index 373d5e6dae..22754e5e0f 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogCore.podspec b/DatadogCore.podspec index 9f0a76ff14..508a83fff7 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index d715a3b295..7035861afd 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => 'https://github.com/DataDog/dd-sdk-ios.git', :tag => s.version.to_s } s.static_framework = true diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index db33ab60a4..ced6b3a5da 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index 06078efc19..e17d1d036e 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index f849a7d198..8b7aec3131 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => 'https://github.com/DataDog/dd-sdk-ios.git', :tag => s.version.to_s } diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index f809da8ffe..7dd1267549 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index bd1b292570..12fd9aed2e 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index eb98a0f6dc..b1687aa013 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index 8728195fca..9e212137c9 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.deprecated_in_favor_of = 'DatadogCrashReporting' diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 98f69f5cdf..2ef9406987 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.deprecated_in_favor_of = 'DatadogObjc' diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index 43b9592963..833b120d14 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 8116e118c2..5a3c91bccd 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/TestUtilities.podspec b/TestUtilities.podspec index 70f81eb5e1..7c4a390ee4 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.swift_version = '5.7.1' s.ios.deployment_target = '12.0' - s.tvos.deployment_target = '11.0' + s.tvos.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } From f7238f01e12c3974505397c1ca63160044cacb0e Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 15:48:15 +0200 Subject: [PATCH 33/71] RUM-4829 Bump `TVOS_DEPLOYMENT_TARGET` for SPM --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 8d7125e24f..4411ae8d09 100644 --- a/Package.swift +++ b/Package.swift @@ -7,7 +7,7 @@ let package = Package( name: "Datadog", platforms: [ .iOS(.v12), - .tvOS(.v11), + .tvOS(.v12), .macOS(.v12) ], products: [ From d1800efc1574807d6337b9df6562db847a80a23f Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 15:50:11 +0200 Subject: [PATCH 34/71] RUM-4829 Bump deployment target in IntegrationTests as tvOS 11.0 is no longer supported by the SDK --- IntegrationTests/xcconfigs/Base.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntegrationTests/xcconfigs/Base.xcconfig b/IntegrationTests/xcconfigs/Base.xcconfig index 1ac835b13c..d0d1d5422a 100644 --- a/IntegrationTests/xcconfigs/Base.xcconfig +++ b/IntegrationTests/xcconfigs/Base.xcconfig @@ -1,6 +1,6 @@ // Minimum iOS deployment targets for building integration tests: IPHONEOS_DEPLOYMENT_TARGET=12.0 -TVOS_DEPLOYMENT_TARGET=11.0 +TVOS_DEPLOYMENT_TARGET=12.0 // Add common settings from Datadog.xcconfig #include "../xcconfigs/Datadog.xcconfig" From 4780395a4253fcd4786d28d8a97669583202e89b Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 15:53:59 +0200 Subject: [PATCH 35/71] =?UTF-8?q?RUM-4829=20CR=20feedback=20-=20rename=20`?= =?UTF-8?q?iOS12CarrierInfoPublisher`=20=E2=86=92=20`CarrierInfoPublisher`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift | 2 +- DatadogCore/Sources/Core/DatadogCore.swift | 2 +- .../DatadogCore/Context/CarrierInfoPublisherTests.swift | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift b/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift index 3369ae2f6e..789fd89534 100644 --- a/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift +++ b/DatadogCore/Sources/Core/Context/CarrierInfoPublisher.swift @@ -12,7 +12,7 @@ import DatadogInternal import CoreTelephony /// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` only when `CTCarrier` has changed (e.g. when the SIM card was swapped). -internal struct iOS12CarrierInfoPublisher: ContextValuePublisher { +internal struct CarrierInfoPublisher: ContextValuePublisher { let initialValue: CarrierInfo? private let networkInfo: CTTelephonyNetworkInfo diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 579c0cfb64..5c01ef8735 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -434,7 +434,7 @@ extension DatadogContextProvider { assign(reader: SCNetworkReachabilityReader(), to: \.networkConnectionInfo) } #if os(iOS) && !targetEnvironment(macCatalyst) && !(swift(>=5.9) && os(visionOS)) - subscribe(\.carrierInfo, to: iOS12CarrierInfoPublisher()) + subscribe(\.carrierInfo, to: CarrierInfoPublisher()) #endif #if os(iOS) && !targetEnvironment(simulator) diff --git a/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift index eca548340f..9e01d5ad10 100644 --- a/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogCore/Context/CarrierInfoPublisherTests.swift @@ -25,7 +25,7 @@ class CarrierInfoPublisherTests: XCTestCase { func testGivenCellularServiceAvailable_itProvidesInitialValue() { // Given - let publisher = iOS12CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) + let publisher = CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) // Then XCTAssertEqual(publisher.initialValue?.carrierName, "Carrier") @@ -35,7 +35,7 @@ class CarrierInfoPublisherTests: XCTestCase { func testGivenCellularServiceUnAvailable_itProvidesNoInitialValue() { // Given - let publisher = iOS12CarrierInfoPublisher(networkInfo: unavailableCTTelephonyNetworkInfo) + let publisher = CarrierInfoPublisher(networkInfo: unavailableCTTelephonyNetworkInfo) // Then XCTAssertNil(publisher.initialValue) @@ -44,7 +44,7 @@ class CarrierInfoPublisherTests: XCTestCase { func testGivenSubscribedInfoProvider_whenCarrierInfoChanges_itNotifiesSubscriber() throws { let expectation = expectation(description: "Notify `CarrierInfo` change") var info: CarrierInfo? = nil - let publisher = iOS12CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) + let publisher = CarrierInfoPublisher(networkInfo: availableCTTelephonyNetworkInfo) // Given publisher.publish { From a6f658a3c25af15a3aa5c06031c4f25542e80edc Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 16:45:52 +0200 Subject: [PATCH 36/71] RUM-4829 Remove code specific to tvOS 11 --- .../NetworkConnectionInfoPublisher.swift | 70 --------------- DatadogCore/Sources/Core/DatadogCore.swift | 7 +- .../NetworkConnectionInfoPublisherTests.swift | 86 ++++--------------- .../SystemFrameworks/CoreTelephonyMocks.swift | 11 +-- .../Sources/Utils/UIKitExtensions.swift | 3 +- .../Recorder/Utilities/UIKitExtensions.swift | 18 ++-- .../Tests/Mocks/UIKitMocks.swift | 8 +- 7 files changed, 29 insertions(+), 174 deletions(-) diff --git a/DatadogCore/Sources/Core/Context/NetworkConnectionInfoPublisher.swift b/DatadogCore/Sources/Core/Context/NetworkConnectionInfoPublisher.swift index 65b0bb64f1..4afafb8e3e 100644 --- a/DatadogCore/Sources/Core/Context/NetworkConnectionInfoPublisher.swift +++ b/DatadogCore/Sources/Core/Context/NetworkConnectionInfoPublisher.swift @@ -6,9 +6,6 @@ import Foundation import DatadogInternal - -// MARK: - iOS 12+ - import Network /// Thread-safe wrapper for `NWPathMonitor`. @@ -20,7 +17,6 @@ import Network /// We found the pulling model to not be thread-safe: accessing `currentPath` properties lead to occasional crashes. /// The `ThreadSafeNWPathMonitor` listens to path updates and synchonizes the values on `.current` property. /// This adds the necessary thread-safety and keeps the convenience of pulling. -@available(iOS 12, tvOS 12, *) internal struct NWPathMonitorPublisher: ContextValuePublisher { private static let defaultQueue = DispatchQueue( label: "com.datadoghq.nw-path-monitor-publisher", @@ -56,7 +52,6 @@ internal struct NWPathMonitorPublisher: ContextValuePublisher { } extension NetworkConnectionInfo { - @available(iOS 12, tvOS 12, *) init(_ path: NWPath) { self.init( reachability: NetworkConnectionInfo.Reachability(path.status), @@ -75,7 +70,6 @@ extension NetworkConnectionInfo { } extension NetworkConnectionInfo.Reachability { - @available(iOS 12, tvOS 12, *) init(_ status: NWPath.Status) { switch status { case .satisfied: self = .yes @@ -87,7 +81,6 @@ extension NetworkConnectionInfo.Reachability { } extension NetworkConnectionInfo.Interface { - @available(iOS 12, tvOS 12, *) init(_ interface: NWInterface.InterfaceType) { switch interface { case .wifi: self = .wifi @@ -99,66 +92,3 @@ extension NetworkConnectionInfo.Interface { } } } - -// MARK: - iOS 11 - -import SystemConfiguration - -internal struct SCNetworkReachabilityReader: ContextValueReader { - private let reachability: SCNetworkReachability - - init(reachability: SCNetworkReachability) { - self.reachability = reachability - } - - init() { - var zero = sockaddr() - zero.sa_len = UInt8(MemoryLayout.size) - zero.sa_family = sa_family_t(AF_INET) - let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero)! // swiftlint:disable:this force_unwrapping - self.init(reachability: reachability) - } - - func read(to receiver: inout NetworkConnectionInfo?) { - receiver = NetworkConnectionInfo(reachability) - } -} - -extension NetworkConnectionInfo { - init(_ reachability: SCNetworkReachability) { - var retrieval = SCNetworkReachabilityFlags() - let flags = (SCNetworkReachabilityGetFlags(reachability, &retrieval)) ? retrieval : nil - self.init( - reachability: .init(flags), - availableInterfaces: NetworkConnectionInfo.Interface(flags).map { [$0] }, - supportsIPv4: nil, - supportsIPv6: nil, - isExpensive: nil, - isConstrained: nil - ) - } -} - -extension NetworkConnectionInfo.Reachability { - init(_ flags: SCNetworkReachabilityFlags?) { - switch flags?.contains(.reachable) { - case .none: self = .maybe - case .some(true): self = .yes - case .some(false): self = .no - } - } -} - -extension NetworkConnectionInfo.Interface { - @available(iOS 2.0, macCatalyst 13.0, *) - init?(_ flags: SCNetworkReachabilityFlags?) { - #if os(iOS) || os(tvOS) - guard let flags = flags, flags.contains(.isWWAN) else { - return nil - } - self = .cellular - #else - self = .other - #endif - } -} diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 5c01ef8735..6c024b0319 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -428,11 +428,8 @@ extension DatadogContextProvider { subscribe(\.launchTime, to: LaunchTimePublisher()) #endif - if #available(iOS 12, tvOS 12, *) { - subscribe(\.networkConnectionInfo, to: NWPathMonitorPublisher()) - } else { - assign(reader: SCNetworkReachabilityReader(), to: \.networkConnectionInfo) - } + subscribe(\.networkConnectionInfo, to: NWPathMonitorPublisher()) + #if os(iOS) && !targetEnvironment(macCatalyst) && !(swift(>=5.9) && os(visionOS)) subscribe(\.carrierInfo, to: CarrierInfoPublisher()) #endif diff --git a/DatadogCore/Tests/Datadog/DatadogCore/Context/NetworkConnectionInfoPublisherTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/Context/NetworkConnectionInfoPublisherTests.swift index 6a9a9d8627..3799e8d404 100644 --- a/DatadogCore/Tests/Datadog/DatadogCore/Context/NetworkConnectionInfoPublisherTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogCore/Context/NetworkConnectionInfoPublisherTests.swift @@ -11,45 +11,19 @@ import DatadogInternal @testable import DatadogCore class NetworkConnectionInfoPublisherTests: XCTestCase { - // MARK: - iOS 12+ - func testNWPathMonitorPublishValue() { - if #available(iOS 12.0, tvOS 12, *) { - let expectation = expectation(description: "NWPathMonitorPublisher publish value") - let publisher = NWPathMonitorPublisher() - publisher.publish { _ in expectation.fulfill() } - waitForExpectations(timeout: 1, handler: nil) - } + let expectation = expectation(description: "NWPathMonitorPublisher publish value") + let publisher = NWPathMonitorPublisher() + publisher.publish { _ in expectation.fulfill() } + waitForExpectations(timeout: 1, handler: nil) } func testNWPathMonitorHandling() { - if #available(iOS 12.0, tvOS 12, *) { - let monitor = NWPathMonitor() - let publisher = NWPathMonitorPublisher(monitor: monitor) - publisher.publish { _ in } - XCTAssertNotNil(monitor.pathUpdateHandler, "`NWPathMonitor` has a handler") - XCTAssertNotNil(monitor.queue, "`NWPathMonitor` is started with synchronization queue") - } - } - - // MARK: - iOS 11 - - func testSCNetworkReachabilityReadValue() { - let reader = SCNetworkReachabilityReader() - var info: NetworkConnectionInfo? = .init( - reachability: .maybe, - availableInterfaces: nil, - supportsIPv4: false, - supportsIPv6: false, - isExpensive: false, - isConstrained: false - ) - - reader.read(to: &info) - XCTAssertNil(info?.supportsIPv4) - XCTAssertNil(info?.supportsIPv6) - XCTAssertNil(info?.isExpensive) - XCTAssertNil(info?.isConstrained) + let monitor = NWPathMonitor() + let publisher = NWPathMonitorPublisher(monitor: monitor) + publisher.publish { _ in } + XCTAssertNotNil(monitor.pathUpdateHandler, "`NWPathMonitor` has a handler") + XCTAssertNotNil(monitor.queue, "`NWPathMonitor` is started with synchronization queue") } } @@ -58,42 +32,16 @@ class NetworkConnectionInfoConversionTests: XCTestCase { typealias Interface = NetworkConnectionInfo.Interface func testNWPathStatus() { - if #available(iOS 12.0, tvOS 12, *) { - XCTAssertEqual(Reachability(.satisfied), .yes) - XCTAssertEqual(Reachability(.unsatisfied), .no) - XCTAssertEqual(Reachability(.requiresConnection), .maybe) - } + XCTAssertEqual(Reachability(.satisfied), .yes) + XCTAssertEqual(Reachability(.unsatisfied), .no) + XCTAssertEqual(Reachability(.requiresConnection), .maybe) } func testNWInterface() { - if #available(iOS 12.0, tvOS 12, *) { - XCTAssertEqual(Interface(.wifi), .wifi) - XCTAssertEqual(Interface(.wiredEthernet), .wiredEthernet) - XCTAssertEqual(Interface(.loopback), .loopback) - XCTAssertEqual(Interface(.cellular), .cellular) - XCTAssertEqual(Interface(.other), .other) - } - } - - func testSCReachability() { - let reachable = SCNetworkReachabilityFlags(arrayLiteral: .reachable) - XCTAssertEqual(Reachability(reachable), .yes) - - let unreachable = SCNetworkReachabilityFlags(arrayLiteral: .connectionOnDemand) - XCTAssertEqual(Reachability(unreachable), .no) - - let null: SCNetworkReachabilityFlags? = nil - XCTAssertEqual(Reachability(null), .maybe) - } - - func testSCInterface() { - let cellular = SCNetworkReachabilityFlags(arrayLiteral: .isWWAN) - XCTAssertEqual(Interface(cellular), .cellular) - - let null: SCNetworkReachabilityFlags? = nil - XCTAssertNil(Interface(null)) - - let nonCellularReachable = SCNetworkReachabilityFlags(arrayLiteral: .reachable) - XCTAssertNil(Interface(nonCellularReachable)) + XCTAssertEqual(Interface(.wifi), .wifi) + XCTAssertEqual(Interface(.wiredEthernet), .wiredEthernet) + XCTAssertEqual(Interface(.loopback), .loopback) + XCTAssertEqual(Interface(.cellular), .cellular) + XCTAssertEqual(Interface(.other), .other) } } diff --git a/DatadogCore/Tests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift b/DatadogCore/Tests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift index 46c17b2ec8..f6f087f923 100644 --- a/DatadogCore/Tests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/SystemFrameworks/CoreTelephonyMocks.swift @@ -53,20 +53,11 @@ class CTTelephonyNetworkInfoMock: CTTelephonyNetworkInfo { "000001": CTCarrierMock(carrierName: newCarrierName, isoCountryCode: newISOCountryCode, allowsVOIP: newAllowsVOIP) ] - if #available(iOS 12.0, *) { - serviceSubscriberCellularProvidersDidUpdateNotifier?("000001") - } + serviceSubscriberCellularProvidersDidUpdateNotifier?("000001") } - // MARK: - iOS 12+ - override var serviceCurrentRadioAccessTechnology: [String: String]? { _serviceCurrentRadioAccessTechnology } override var serviceSubscriberCellularProviders: [String: CTCarrier]? { _serviceSubscriberCellularProviders } - - // MARK: - Prior to iOS 12 - - override var currentRadioAccessTechnology: String? { _serviceCurrentRadioAccessTechnology?.first?.value } - override var subscriberCellularProvider: CTCarrier? { _serviceSubscriberCellularProviders?.first?.value } } #endif diff --git a/DatadogRUM/Sources/Utils/UIKitExtensions.swift b/DatadogRUM/Sources/Utils/UIKitExtensions.swift index ec9bee0e72..99471fce4f 100644 --- a/DatadogRUM/Sources/Utils/UIKitExtensions.swift +++ b/DatadogRUM/Sources/Utils/UIKitExtensions.swift @@ -16,7 +16,6 @@ internal extension UIViewController { internal extension Bundle { var isUIKit: Bool { - return bundleURL.lastPathComponent == "UIKitCore.framework" // on iOS 12+ - || bundleURL.lastPathComponent == "UIKit.framework" // on iOS 11 + return bundleURL.lastPathComponent == "UIKitCore.framework" } } diff --git a/DatadogSessionReplay/Sources/Recorder/Utilities/UIKitExtensions.swift b/DatadogSessionReplay/Sources/Recorder/Utilities/UIKitExtensions.swift index 16b09daec5..917e4da5e8 100644 --- a/DatadogSessionReplay/Sources/Recorder/Utilities/UIKitExtensions.swift +++ b/DatadogSessionReplay/Sources/Recorder/Utilities/UIKitExtensions.swift @@ -9,29 +9,21 @@ import UIKit internal extension UITraitEnvironment { var usesDarkMode: Bool { - if #available(iOS 12.0, *) { - return traitCollection.userInterfaceStyle == .dark - } else { - return false // assume "no" - } + return traitCollection.userInterfaceStyle == .dark } } /// Sensitive text content types as defined in Session Replay. internal let sensitiveContentTypes: Set = { - var all: Set = [ + return [ .password, .emailAddress, .telephoneNumber, .addressCity, .addressState, .addressCityAndState, .fullStreetAddress, .streetAddressLine1, .streetAddressLine2, .postalCode, - .creditCardNumber + .creditCardNumber, + .newPassword, + .oneTimeCode, ] - - if #available(iOS 12.0, *) { - all.formUnion([.newPassword, .oneTimeCode]) - } - - return all }() internal extension UITextInputTraits { diff --git a/DatadogSessionReplay/Tests/Mocks/UIKitMocks.swift b/DatadogSessionReplay/Tests/Mocks/UIKitMocks.swift index 83e320b352..2607b80e52 100644 --- a/DatadogSessionReplay/Tests/Mocks/UIKitMocks.swift +++ b/DatadogSessionReplay/Tests/Mocks/UIKitMocks.swift @@ -89,17 +89,15 @@ extension UITextContentType: RandomMockable { .jobTitle, .organizationName, .location, .fullStreetAddress, .streetAddressLine1, .streetAddressLine2, .addressCity, .addressState, .addressCityAndState, .sublocality, .countryName, .postalCode, .telephoneNumber, .emailAddress, .URL, .creditCardNumber, - .username, .password + .username, .password, + .newPassword, + .oneTimeCode, ] if #available(iOS 15.0, tvOS 15.0, *) { all.formUnion([.shipmentTrackingNumber, .flightNumber, .dateTime]) } - if #available(iOS 12.0, tvOS 12.0, *) { - all.formUnion([.newPassword, .oneTimeCode]) - } - return all } From c982eb3cda9e88ef941b89f49c1c5d8fa0197121 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 16:46:37 +0200 Subject: [PATCH 37/71] RUM-4829 Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c999bce385..e2eadea05a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -- [IMPROVEMENT] Bump `IPHONEOS_DEPLOYMENT_TARGET` from 11 to 12. See [#1891][] +- [IMPROVEMENT] Bump `IPHONEOS_DEPLOYMENT_TARGET` and `TVOS_DEPLOYMENT_TARGET` from 11 to 12. See [#1891][] # 2.12.0 / 03-06-2024 From 40d07972709ab1d0a13888d5c7a62cd844f4e235 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 17:58:17 +0200 Subject: [PATCH 38/71] RUM-4829 Fail linter on first error --- bitrise.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/bitrise.yml b/bitrise.yml index e1cb0a9620..2e5e09f36f 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -167,7 +167,6 @@ workflows: - reporter: emoji - swiftlint@0.8.0: title: Lint Tests/* - is_always_run: true inputs: - strict: 'yes' - linting_path: "$BITRISE_SOURCE_DIR" From 7710653531cff28316c8749c947ec84cf4f772d7 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 12 Jun 2024 09:16:15 +0100 Subject: [PATCH 39/71] RUM-4824 Make color SR identifier lazy --- .../Recorder/Utilities/UIImage+SessionReplay.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift b/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift index 726df9f5d4..0a5466c80e 100644 --- a/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift +++ b/DatadogSessionReplay/Sources/Recorder/Utilities/UIImage+SessionReplay.swift @@ -37,6 +37,16 @@ extension DatadogExtension where ExtendedType: UIImage { extension UIColor { var srIdentifier: String { + if let hash = objc_getAssociatedObject(self, &srIdentifierKey) as? String { + return hash + } else { + let hash = computeIdentifier() + objc_setAssociatedObject(self, &srIdentifierKey, hash, .OBJC_ASSOCIATION_RETAIN) + return hash + } + } + + private func computeIdentifier() -> String { var r: CGFloat = 0 var g: CGFloat = 0 var b: CGFloat = 0 From f11d2a15db3e289cdd60a18e905d127eac91882c Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 22 May 2024 16:15:52 +0200 Subject: [PATCH 40/71] RUM-1660 Add `BundleType` to `DatadogContext` so it can be available for each product. --- Datadog/Datadog.xcodeproj/project.pbxproj | 18 +++++++---- DatadogCore/Sources/Core/DatadogCore.swift | 3 +- DatadogCore/Sources/Datadog.swift | 3 +- .../Datadog/DatadogConfigurationTests.swift | 30 +++++++++++++++++++ .../Sources/Context}/BundleType.swift | 9 ++++-- .../Sources/Context/DatadogContext.swift | 5 ++++ .../Tests/Context/BundleTypeTests.swift | 22 ++++++++++++++ TestUtilities/Mocks/DatadogContextMock.swift | 13 ++++++++ 8 files changed, 93 insertions(+), 10 deletions(-) rename {DatadogCore/Sources => DatadogInternal/Sources/Context}/BundleType.swift (59%) create mode 100644 DatadogInternal/Tests/Context/BundleTypeTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index dba48a02b1..42e32e06fd 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -351,7 +351,6 @@ 614B78F1296D7B63009C6B92 /* LowPowerModePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EC296D7B63009C6B92 /* LowPowerModePublisherTests.swift */; }; 614B78F2296D7B63009C6B92 /* LowPowerModePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614B78EC296D7B63009C6B92 /* LowPowerModePublisherTests.swift */; }; 614CADD72510BAC000B93D2D /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614CADD62510BAC000B93D2D /* Environment.swift */; }; - 614E9EB3244719FA007EE3E1 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614E9EB2244719FA007EE3E1 /* BundleType.swift */; }; 614ED36C260352DC00C8C519 /* CrashReporter.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 614ED36B260352DC00C8C519 /* CrashReporter.xcframework */; }; 615192CD2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CC2BD6948B0005A782 /* HTTPHeadersWriterTests.swift */; }; 615192CE2BD6948B0005A782 /* HTTPHeadersWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 615192CC2BD6948B0005A782 /* HTTPHeadersWriterTests.swift */; }; @@ -424,6 +423,10 @@ 6174D6062BFB9D6400EC7469 /* DDWebViewTracking+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */; }; 6174D60C2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; 6174D60D2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; + 6174D6132BFDF16C00EC7469 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6122BFDF16C00EC7469 /* BundleType.swift */; }; + 6174D6142BFDF16C00EC7469 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6122BFDF16C00EC7469 /* BundleType.swift */; }; + 6174D6162BFDF29B00EC7469 /* BundleTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */; }; + 6174D6172BFDF29B00EC7469 /* BundleTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */; }; 6174D61D2C007B3300EC7469 /* ModuleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61C2C007B3300EC7469 /* ModuleName.swift */; }; 6174D61E2C007B3300EC7469 /* ModuleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61C2C007B3300EC7469 /* ModuleName.swift */; }; 6175922B2A6FA8EE0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; @@ -1395,7 +1398,6 @@ D2CB6E9B27C50EAE00A62B57 /* FilesOrchestrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BA92423979B00786299 /* FilesOrchestrator.swift */; }; D2CB6EA727C50EAE00A62B57 /* Versioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D5AEA624B4D45A007F194B /* Versioning.swift */; }; D2CB6EA827C50EAE00A62B57 /* URLSessionClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BB22423979B00786299 /* URLSessionClient.swift */; }; - D2CB6EAF27C50EAE00A62B57 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614E9EB2244719FA007EE3E1 /* BundleType.swift */; }; D2CB6EB327C50EAE00A62B57 /* KronosNTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0CE277B23F0008BE766 /* KronosNTPClient.swift */; }; D2CB6EBA27C50EAE00A62B57 /* DataUploadConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BAF2423979B00786299 /* DataUploadConditions.swift */; }; D2CB6EBF27C50EAE00A62B57 /* KronosData+Bytes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D3E0CD277B23F0008BE766 /* KronosData+Bytes.swift */; }; @@ -2350,7 +2352,6 @@ 614B78EA296D7B63009C6B92 /* DatadogCoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatadogCoreTests.swift; sourceTree = ""; }; 614B78EC296D7B63009C6B92 /* LowPowerModePublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LowPowerModePublisherTests.swift; sourceTree = ""; }; 614CADD62510BAC000B93D2D /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; - 614E9EB2244719FA007EE3E1 /* BundleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleType.swift; sourceTree = ""; }; 614ED36B260352DC00C8C519 /* CrashReporter.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = CrashReporter.xcframework; path = ../Carthage/Build/CrashReporter.xcframework; sourceTree = ""; }; 615192CC2BD6948B0005A782 /* HTTPHeadersWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeadersWriterTests.swift; sourceTree = ""; }; 615192CF2BD6B7C90005A782 /* DatadogTracer+InjectAndExtract.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatadogTracer+InjectAndExtract.swift"; sourceTree = ""; }; @@ -2416,6 +2417,8 @@ 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebViewTracking+objc.swift"; sourceTree = ""; }; 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDWebViewTracking+apiTests.m"; sourceTree = ""; }; 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKMetricFields.swift; sourceTree = ""; }; + 6174D6122BFDF16C00EC7469 /* BundleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleType.swift; sourceTree = ""; }; + 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleTypeTests.swift; sourceTree = ""; }; 6174D61C2C007B3300EC7469 /* ModuleName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleName.swift; sourceTree = ""; }; 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceContext.swift; sourceTree = ""; }; 617699172A860D9D0030022B /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; @@ -4032,7 +4035,6 @@ D286626D2A43487500852CE3 /* Datadog.swift */, D2FB125C292FBB56005B13F8 /* Datadog+Internal.swift */, E1D5AEA624B4D45A007F194B /* Versioning.swift */, - 614E9EB2244719FA007EE3E1 /* BundleType.swift */, 61BB2B1A244A185D009F3F56 /* PerformancePreset.swift */, 61133B9E2423979B00786299 /* Core */, 61216277247D1F2100AC5D67 /* FeaturesIntegration */, @@ -5709,6 +5711,7 @@ D23039BC298D5235001A1FA3 /* DeviceInfo.swift */, D23039BE298D5235001A1FA3 /* LaunchTime.swift */, D2F8235229915E12003C7E99 /* DatadogSite.swift */, + 6174D6122BFDF16C00EC7469 /* BundleType.swift */, ); path = Context; sourceTree = ""; @@ -6238,6 +6241,7 @@ children = ( D2DA239D298D58F300C6C7E6 /* AppStateHistoryTests.swift */, D2DA239E298D58F300C6C7E6 /* DeviceInfoTests.swift */, + 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */, ); path = Context; sourceTree = ""; @@ -7916,7 +7920,6 @@ 61DA8CAF28620C760074A606 /* Cryptography.swift in Sources */, E1D5AEA724B4D45B007F194B /* Versioning.swift in Sources */, 61133BD82423979B00786299 /* URLSessionClient.swift in Sources */, - 614E9EB3244719FA007EE3E1 /* BundleType.swift in Sources */, 61D3E0D8277B23F1008BE766 /* KronosNTPClient.swift in Sources */, D20605B22874E1660047275C /* CarrierInfoPublisher.swift in Sources */, D2A1EE32287DA51900D28DFB /* UserInfoPublisher.swift in Sources */, @@ -8502,6 +8505,7 @@ D23039EA298D5236001A1FA3 /* DeviceInfo.swift in Sources */, D2EBEE2329BA160F00B15732 /* B3HTTPHeadersReader.swift in Sources */, D23039F8298D5236001A1FA3 /* InternalLogger.swift in Sources */, + 6174D6132BFDF16C00EC7469 /* BundleType.swift in Sources */, D2303A01298D5236001A1FA3 /* DateFormatting.swift in Sources */, 3C9B27252B9F174700569C07 /* SpanID.swift in Sources */, D2216EC02A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, @@ -9147,7 +9151,6 @@ D20605A4287464F40047275C /* ContextValuePublisher.swift in Sources */, D2CB6EA727C50EAE00A62B57 /* Versioning.swift in Sources */, D2CB6EA827C50EAE00A62B57 /* URLSessionClient.swift in Sources */, - D2CB6EAF27C50EAE00A62B57 /* BundleType.swift in Sources */, D2CB6EB327C50EAE00A62B57 /* KronosNTPClient.swift in Sources */, D2CB6EBA27C50EAE00A62B57 /* DataUploadConditions.swift in Sources */, D2CB6EBF27C50EAE00A62B57 /* KronosData+Bytes.swift in Sources */, @@ -9437,6 +9440,7 @@ D2DA236D298D57AA00C6C7E6 /* DeviceInfo.swift in Sources */, D2EBEE3129BA161100B15732 /* B3HTTPHeadersReader.swift in Sources */, D2DA236E298D57AA00C6C7E6 /* InternalLogger.swift in Sources */, + 6174D6142BFDF16C00EC7469 /* BundleType.swift in Sources */, D2DA236F298D57AA00C6C7E6 /* DateFormatting.swift in Sources */, 3C9B27262B9F174700569C07 /* SpanID.swift in Sources */, D2216EC12A94DE2900ADAEC8 /* FeatureBaggage.swift in Sources */, @@ -9495,6 +9499,7 @@ D2DA23A3298D58F400C6C7E6 /* AnyEncodableTests.swift in Sources */, 3CCECDAF2BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */, D263BCB429DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, + 6174D6162BFDF29B00EC7469 /* BundleTypeTests.swift in Sources */, 3C0D5DF52A5443B100446CF9 /* DataFormatTests.swift in Sources */, D2EBEE4429BA168200B15732 /* TraceIDTests.swift in Sources */, D2EBEE4329BA168200B15732 /* TraceIDGeneratorTests.swift in Sources */, @@ -9543,6 +9548,7 @@ D2DA23B1298D59DC00C6C7E6 /* AnyEncodableTests.swift in Sources */, 3CCECDB02BC688120013C125 /* SpanIDGeneratorTests.swift in Sources */, D263BCB529DB014900FA0E21 /* FixedWidthInteger+ConvenienceTests.swift in Sources */, + 6174D6172BFDF29B00EC7469 /* BundleTypeTests.swift in Sources */, 3C0D5DF62A5443B100446CF9 /* DataFormatTests.swift in Sources */, D2EBEE4629BA168400B15732 /* TraceIDTests.swift in Sources */, D2EBEE4529BA168400B15732 /* TraceIDGeneratorTests.swift in Sources */, diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index a65d0b89cd..74338267e0 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -109,7 +109,6 @@ internal final class DatadogCore { self.isRunFromExtension = isRunFromExtension self.applicationVersionPublisher = ApplicationVersionPublisher(version: applicationVersion) self.consentPublisher = TrackingConsentPublisher(consent: initialConsent) - self.contextProvider.subscribe(\.userInfo, to: userInfoPublisher) self.contextProvider.subscribe(\.version, to: applicationVersionPublisher) self.contextProvider.subscribe(\.trackingConsent, to: consentPublisher) @@ -391,6 +390,7 @@ extension DatadogContextProvider { ciAppOrigin: String?, applicationName: String, applicationBundleIdentifier: String, + applicationBundleType: BundleType, applicationVersion: String, sdkInitDate: Date, device: DeviceInfo, @@ -411,6 +411,7 @@ extension DatadogContextProvider { ciAppOrigin: ciAppOrigin, applicationName: applicationName, applicationBundleIdentifier: applicationBundleIdentifier, + applicationBundleType: applicationBundleType, sdkInitDate: dateProvider.now, device: device, nativeSourceOverride: nativeSourceOverride, diff --git a/DatadogCore/Sources/Datadog.swift b/DatadogCore/Sources/Datadog.swift index 1c9f2f77b0..1b06cbf415 100644 --- a/DatadogCore/Sources/Datadog.swift +++ b/DatadogCore/Sources/Datadog.swift @@ -416,7 +416,7 @@ public enum Datadog { ?? "0" let bundleName = configuration.bundle.object(forInfoDictionaryKey: "CFBundleExecutable") as? String - let bundleType: BundleType = configuration.bundle.bundlePath.hasSuffix(".appex") ? .iOSAppExtension : .iOSApp + let bundleType = BundleType(bundle: configuration.bundle) let bundleIdentifier = configuration.bundle.bundleIdentifier ?? "unknown" let service = configuration.service ?? configuration.bundle.bundleIdentifier ?? "ios" let source = configuration.additionalConfiguration[CrossPlatformAttributes.ddsource] as? String ?? "ios" @@ -459,6 +459,7 @@ public enum Datadog { ciAppOrigin: CITestIntegration.active?.origin, applicationName: bundleName ?? bundleType.rawValue, applicationBundleIdentifier: bundleIdentifier, + applicationBundleType: bundleType, applicationVersion: applicationVersion, sdkInitDate: configuration.dateProvider.now, device: DeviceInfo(), diff --git a/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift b/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift index 3bcd82b16a..7021aae826 100644 --- a/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogConfigurationTests.swift @@ -316,6 +316,36 @@ class DatadogConfigurationTests: XCTestCase { XCTAssertEqual(context.applicationBundleIdentifier, "unknown") } + func testiOSAppBundleType() throws { + var configuration = defaultConfig + configuration.bundle = .mockWith(bundlePath: "bundle.path.app") + + Datadog.initialize( + with: configuration, + trackingConsent: .mockRandom() + ) + defer { Datadog.flushAndDeinitialize() } + + let core = try XCTUnwrap(CoreRegistry.default as? DatadogCore) + let context = core.contextProvider.read() + XCTAssertEqual(context.applicationBundleType, .iOSApp) + } + + func testiOSAppExtensionBundleType() throws { + var configuration = defaultConfig + configuration.bundle = .mockWith(bundlePath: "bundle.path.appex") + + Datadog.initialize( + with: configuration, + trackingConsent: .mockRandom() + ) + defer { Datadog.flushAndDeinitialize() } + + let core = try XCTUnwrap(CoreRegistry.default as? DatadogCore) + let context = core.contextProvider.read() + XCTAssertEqual(context.applicationBundleType, .iOSAppExtension) + } + func testEnvironment() throws { func verify(validEnv env: String) throws { Datadog.initialize( diff --git a/DatadogCore/Sources/BundleType.swift b/DatadogInternal/Sources/Context/BundleType.swift similarity index 59% rename from DatadogCore/Sources/BundleType.swift rename to DatadogInternal/Sources/Context/BundleType.swift index aa2b18ff25..5ab6a16a1d 100644 --- a/DatadogCore/Sources/BundleType.swift +++ b/DatadogInternal/Sources/Context/BundleType.swift @@ -6,8 +6,13 @@ import Foundation -/// A type of the bundle running the SDK. -internal enum BundleType: String { +public enum BundleType: String { + /// An iOS application. case iOSApp + /// An iOS app extension. case iOSAppExtension + + public init(bundle: Bundle) { + self = bundle.bundlePath.hasSuffix(".appex") ? .iOSAppExtension : .iOSApp + } } diff --git a/DatadogInternal/Sources/Context/DatadogContext.swift b/DatadogInternal/Sources/Context/DatadogContext.swift index 83f97ea759..45d7802f0d 100644 --- a/DatadogInternal/Sources/Context/DatadogContext.swift +++ b/DatadogInternal/Sources/Context/DatadogContext.swift @@ -62,6 +62,9 @@ public struct DatadogContext { /// The bundle identifier, read from `Info.plist` (`CFBundleIdentifier`). public let applicationBundleIdentifier: String + /// The type of the bundle running the SDK. + public let applicationBundleType: BundleType + /// Date of SDK initialization measured in device time (without NTP correction). public let sdkInitDate: Date @@ -125,6 +128,7 @@ public struct DatadogContext { serverTimeOffset: TimeInterval = .zero, applicationName: String, applicationBundleIdentifier: String, + applicationBundleType: BundleType, sdkInitDate: Date, device: DeviceInfo, nativeSourceOverride: String? = nil, @@ -152,6 +156,7 @@ public struct DatadogContext { self.serverTimeOffset = serverTimeOffset self.applicationName = applicationName self.applicationBundleIdentifier = applicationBundleIdentifier + self.applicationBundleType = applicationBundleType self.sdkInitDate = sdkInitDate self.device = device self.nativeSourceOverride = nativeSourceOverride diff --git a/DatadogInternal/Tests/Context/BundleTypeTests.swift b/DatadogInternal/Tests/Context/BundleTypeTests.swift new file mode 100644 index 0000000000..d0d3e92589 --- /dev/null +++ b/DatadogInternal/Tests/Context/BundleTypeTests.swift @@ -0,0 +1,22 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import DatadogInternal + +class BundleTypeTests: XCTestCase { + func testiOSAppBundleType() { + let bundle: Bundle = .mockWith(bundlePath: "bundle.path.app") + let bundleType = BundleType(bundle: bundle) + XCTAssertEqual(bundleType, .iOSApp) + } + + func testiOSAppExtensionBundleType() { + let bundle: Bundle = .mockWith(bundlePath: "bundle.path.appex") + let bundleType = BundleType(bundle: bundle) + XCTAssertEqual(bundleType, .iOSAppExtension) + } +} diff --git a/TestUtilities/Mocks/DatadogContextMock.swift b/TestUtilities/Mocks/DatadogContextMock.swift index a31d66566e..a396203f1b 100644 --- a/TestUtilities/Mocks/DatadogContextMock.swift +++ b/TestUtilities/Mocks/DatadogContextMock.swift @@ -25,6 +25,7 @@ extension DatadogContext: AnyMockable { serverTimeOffset: TimeInterval = .zero, applicationName: String = .mockAny(), applicationBundleIdentifier: String = .mockAny(), + applicationBundleType: BundleType = .mockAny(), sdkInitDate: Date = Date(), nativeSourceOverride: String? = nil, device: DeviceInfo = .mockAny(), @@ -53,6 +54,7 @@ extension DatadogContext: AnyMockable { serverTimeOffset: serverTimeOffset, applicationName: applicationName, applicationBundleIdentifier: applicationBundleIdentifier, + applicationBundleType: applicationBundleType, sdkInitDate: sdkInitDate, device: device, nativeSourceOverride: nativeSourceOverride, @@ -84,6 +86,7 @@ extension DatadogContext: AnyMockable { serverTimeOffset: .mockRandomInThePast(), applicationName: .mockRandom(), applicationBundleIdentifier: .mockRandom(), + applicationBundleType: .mockRandom(), sdkInitDate: .mockRandomInThePast(), device: .mockRandom(), userInfo: .mockRandom(), @@ -109,6 +112,16 @@ extension DatadogSite: AnyMockable, RandomMockable { } } +extension BundleType: AnyMockable, RandomMockable { + public static func mockAny() -> Self { + return .iOSApp + } + + public static func mockRandom() -> Self { + return [.iOSApp, .iOSAppExtension].randomElement()! + } +} + extension DeviceInfo { public static func mockAny() -> DeviceInfo { return .mockWith() From 83285c0ba176ff3c47952388afbe01d6ba34c138 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 23 May 2024 14:46:06 +0200 Subject: [PATCH 41/71] RUM-1660 Define `SessionEndedMetric` and DI controller so it can be easily inejcted to different parts of RUM. --- Datadog/Datadog.xcodeproj/project.pbxproj | 34 ++ .../SDKMetrics/SessionEndedMetric.swift | 253 +++++++++++++ .../SessionEndedMetricController.swift | 53 +++ .../Tests/Mocks/RUMDataModelMocks.swift | 12 +- .../SDKMetrics/SessionEndedMetricTests.swift | 356 ++++++++++++++++++ 5 files changed, 704 insertions(+), 4 deletions(-) create mode 100644 DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift create mode 100644 DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift create mode 100644 DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 42e32e06fd..5ea418df5c 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -423,12 +423,18 @@ 6174D6062BFB9D6400EC7469 /* DDWebViewTracking+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */; }; 6174D60C2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; 6174D60D2BFDDEDF00EC7469 /* SDKMetricFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */; }; + 6174D6102BFDEA4600EC7469 /* SessionEndedMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60F2BFDEA4600EC7469 /* SessionEndedMetric.swift */; }; + 6174D6112BFDEA4600EC7469 /* SessionEndedMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D60F2BFDEA4600EC7469 /* SessionEndedMetric.swift */; }; 6174D6132BFDF16C00EC7469 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6122BFDF16C00EC7469 /* BundleType.swift */; }; 6174D6142BFDF16C00EC7469 /* BundleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6122BFDF16C00EC7469 /* BundleType.swift */; }; 6174D6162BFDF29B00EC7469 /* BundleTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */; }; 6174D6172BFDF29B00EC7469 /* BundleTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */; }; + 6174D61A2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6192BFE449300EC7469 /* SessionEndedMetricTests.swift */; }; + 6174D61B2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D6192BFE449300EC7469 /* SessionEndedMetricTests.swift */; }; 6174D61D2C007B3300EC7469 /* ModuleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61C2C007B3300EC7469 /* ModuleName.swift */; }; 6174D61E2C007B3300EC7469 /* ModuleName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61C2C007B3300EC7469 /* ModuleName.swift */; }; + 6174D6202C009C6300EC7469 /* SessionEndedMetricController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61F2C009C6300EC7469 /* SessionEndedMetricController.swift */; }; + 6174D6212C009C6300EC7469 /* SessionEndedMetricController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6174D61F2C009C6300EC7469 /* SessionEndedMetricController.swift */; }; 6175922B2A6FA8EE0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175922D2A6FADDD0073F431 /* DatadogSessionReplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6133D1F52A6ED9E100384BEF /* DatadogSessionReplay.framework */; }; 6175C3512BCE66DB006FAAB0 /* TraceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */; }; @@ -2417,9 +2423,12 @@ 6174D6032BFB9AB600EC7469 /* WebViewTracking+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebViewTracking+objc.swift"; sourceTree = ""; }; 6174D6052BFB9D5500EC7469 /* DDWebViewTracking+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDWebViewTracking+apiTests.m"; sourceTree = ""; }; 6174D60B2BFDDEDF00EC7469 /* SDKMetricFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKMetricFields.swift; sourceTree = ""; }; + 6174D60F2BFDEA4600EC7469 /* SessionEndedMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetric.swift; sourceTree = ""; }; 6174D6122BFDF16C00EC7469 /* BundleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleType.swift; sourceTree = ""; }; 6174D6152BFDF29B00EC7469 /* BundleTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleTypeTests.swift; sourceTree = ""; }; + 6174D6192BFE449300EC7469 /* SessionEndedMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricTests.swift; sourceTree = ""; }; 6174D61C2C007B3300EC7469 /* ModuleName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleName.swift; sourceTree = ""; }; + 6174D61F2C009C6300EC7469 /* SessionEndedMetricController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricController.swift; sourceTree = ""; }; 6175C3502BCE66DB006FAAB0 /* TraceContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceContext.swift; sourceTree = ""; }; 617699172A860D9D0030022B /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 6176991A2A86121B0030022B /* HTTPClientMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClientMock.swift; sourceTree = ""; }; @@ -4818,6 +4827,23 @@ path = SDKMetrics; sourceTree = ""; }; + 6174D60E2BFDEA1F00EC7469 /* SDKMetrics */ = { + isa = PBXGroup; + children = ( + 6174D60F2BFDEA4600EC7469 /* SessionEndedMetric.swift */, + 6174D61F2C009C6300EC7469 /* SessionEndedMetricController.swift */, + ); + path = SDKMetrics; + sourceTree = ""; + }; + 6174D6182BFE447600EC7469 /* SDKMetrics */ = { + isa = PBXGroup; + children = ( + 6174D6192BFE449300EC7469 /* SessionEndedMetricTests.swift */, + ); + path = SDKMetrics; + sourceTree = ""; + }; 617699162A8608C20030022B /* Context */ = { isa = PBXGroup; children = ( @@ -6042,6 +6068,7 @@ 615950EC291C057D00470E0C /* Integrations */, 61B22E5824F3E6A700DC26D2 /* Debugging */, D29A9F8B29DD860A005C54A4 /* Utils */, + 6174D60E2BFDEA1F00EC7469 /* SDKMetrics */, ); name = DatadogRUM; path = ../DatadogRUM/Sources; @@ -6066,6 +6093,7 @@ 61411B0E24EC15940012EAB2 /* Utils */, 61C713C92A3DC22700FA735A /* RUMTests.swift */, 6188697B2A4376F700E8996B /* RUMConfigurationTests.swift */, + 6174D6182BFE447600EC7469 /* SDKMetrics */, ); name = DatadogRUMTests; path = ../DatadogRUM/Tests; @@ -8603,6 +8631,7 @@ 6194B92E2BB43F9C00179430 /* FatalErrorContextNotifier.swift in Sources */, 6167E6D72B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */, 61C713A42A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */, + 6174D6112BFDEA4600EC7469 /* SessionEndedMetric.swift in Sources */, 3C0D5DED2A54405A00446CF9 /* RUMViewEventsFilter.swift in Sources */, D23F8E7629DDCD28001CFAE8 /* RUMConnectivityInfoProvider.swift in Sources */, D23F8E7729DDCD28001CFAE8 /* UIKitRUMViewsPredicate.swift in Sources */, @@ -8628,6 +8657,7 @@ D23F8E8A29DDCD28001CFAE8 /* RUMEventsMapper.swift in Sources */, D23F8E8B29DDCD28001CFAE8 /* RUMContext.swift in Sources */, D23F8E8C29DDCD28001CFAE8 /* RUMBaggageKeys.swift in Sources */, + 6174D6212C009C6300EC7469 /* SessionEndedMetricController.swift in Sources */, D23F8E8D29DDCD28001CFAE8 /* VitalRefreshRateReader.swift in Sources */, D23F8E8E29DDCD28001CFAE8 /* UIKitRUMUserActionsHandler.swift in Sources */, D23F8E8F29DDCD28001CFAE8 /* RUMUUIDGenerator.swift in Sources */, @@ -8659,6 +8689,7 @@ D23F8EB129DDCD38001CFAE8 /* RUMViewScopeTests.swift in Sources */, D224431029E977A100274EC7 /* TelemetryReceiverTests.swift in Sources */, D23F8EB229DDCD38001CFAE8 /* ValuePublisherTests.swift in Sources */, + 6174D61B2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */, 61181CDD2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift in Sources */, 61C713BD2A3C95AD00FA735A /* RUMInstrumentationTests.swift in Sources */, D23F8EB329DDCD38001CFAE8 /* ErrorMessageReceiverTests.swift in Sources */, @@ -8917,6 +8948,7 @@ 6194B92D2BB43F9C00179430 /* FatalErrorContextNotifier.swift in Sources */, 6167E6D62B7F8C3400C3CA2D /* AppHangsWatchdogThread.swift in Sources */, 61C713A32A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */, + 6174D6102BFDEA4600EC7469 /* SessionEndedMetric.swift in Sources */, 3C0D5DEC2A54405A00446CF9 /* RUMViewEventsFilter.swift in Sources */, D29A9F5829DD85BB005C54A4 /* RUMConnectivityInfoProvider.swift in Sources */, D29A9F5E29DD85BB005C54A4 /* UIKitRUMViewsPredicate.swift in Sources */, @@ -8942,6 +8974,7 @@ D29A9F7D29DD85BB005C54A4 /* RUMEventsMapper.swift in Sources */, D29A9F5029DD85BA005C54A4 /* RUMContext.swift in Sources */, D29A9F8329DD85BB005C54A4 /* RUMBaggageKeys.swift in Sources */, + 6174D6202C009C6300EC7469 /* SessionEndedMetricController.swift in Sources */, D29A9F8929DD85BB005C54A4 /* VitalRefreshRateReader.swift in Sources */, D29A9F6929DD85BB005C54A4 /* UIKitRUMUserActionsHandler.swift in Sources */, D29A9F5229DD85BB005C54A4 /* RUMUUIDGenerator.swift in Sources */, @@ -8973,6 +9006,7 @@ D29A9FB829DDB483005C54A4 /* RUMViewScopeTests.swift in Sources */, D224430F29E9779F00274EC7 /* TelemetryReceiverTests.swift in Sources */, D29A9F9D29DDB483005C54A4 /* ValuePublisherTests.swift in Sources */, + 6174D61A2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */, 61181CDC2BF35BC000632A7A /* FatalErrorContextNotifierTests.swift in Sources */, 61C713BC2A3C95AD00FA735A /* RUMInstrumentationTests.swift in Sources */, D29A9FBB29DDB483005C54A4 /* ErrorMessageReceiverTests.swift in Sources */, diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift new file mode 100644 index 0000000000..66b276f950 --- /dev/null +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift @@ -0,0 +1,253 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal + +/// An object tracking the state of RUM session and exporting attributes for "RUM Session Ended" telemetry. +internal final class SessionEndedMetric { + /// Definition of fields in "RUM Session Ended" telemetry, following the "RUM Session Ended" telemetry spec. + internal enum Constants { + /// The name of this metric, included in telemetry log. + /// Note: the "[Mobile Metric]" prefix is added when sending this telemetry in RUM. + static let name = "RUM Session Ended" + /// Metric type value. + static let typeValue = "rum session ended" + /// Namespace for bundling metric attributes ("rse" = "RUM Session Ended"). + static let rseKey = "rse" + /// Key referencing the session ID (`String`) that the metric refers to. + static let sessionIDKey = "sessionID" + } + + /// An ID of the session being tracked through this metric object. + private let sessionID: String + + /// The type of OS component where the session was tracked. + private let bundleType: BundleType + + /// The session precondition that led to the creation of this session. + private let precondition: RUMSessionPrecondition? + + private struct TrackedViewInfo { + let viewURL: String + let startMs: Int64 + var durationNs: Int64 + + // TODO: RUM-4591 Track diagnostic attributes: + // - `instrumentationType`: manual | uikit | swiftui + } + + /// Stores information about tracked views, referencing them by their view ID. + @ReadWriteLock + private var trackedViews: [String: TrackedViewInfo] = [:] + + /// Info about the first tracked view. + @ReadWriteLock + private var firstTrackedView: TrackedViewInfo? + + /// Info about the last tracked view. + @ReadWriteLock + private var lastTrackedView: TrackedViewInfo? + + /// Tracks the number of SDK errors by their kind. + @ReadWriteLock + private var trackedSDKErrors: [String: Int] = [:] + + /// Indicates if the session was stopped through `stopSession()` API. + @ReadWriteLock + private var wasStopped: Bool = false + + // TODO: RUM-4591 Track diagnostic attributes: + // - no_view_events_count + // - has_background_events_tracking_enabled + // - has_replay + // - ntp_offset + + // MARK: - Tracking Metric State + + /// Initializer. + /// - Parameters: + /// - sessionID: An ID of the session that is being tracked with this metric. + /// - precondition: The precondition that led to starting this session. + /// - context: The SDK context at the moment of starting this session. + init( + sessionID: String, + precondition: RUMSessionPrecondition?, + context: DatadogContext + ) { + self.sessionID = sessionID + self.bundleType = context.applicationBundleType + self.precondition = precondition + } + + /// Tracks the view event that occurred during the session. + func track(view: RUMViewEvent) { + guard view.session.id == sessionID else { + return // sanity check, unexpected + } + + var info = trackedViews[view.view.id] ?? TrackedViewInfo( + viewURL: view.view.url, + startMs: view.date, + durationNs: view.view.timeSpent + ) + + info.durationNs = view.view.timeSpent + trackedViews[view.view.id] = info + + if firstTrackedView == nil { + firstTrackedView = info + } + lastTrackedView = info + } + + /// Tracks the kind of SDK error that occurred during the session. + func track(sdkErrorKind: String) { + if let count = trackedSDKErrors[sdkErrorKind] { + trackedSDKErrors[sdkErrorKind] = count + 1 + } else { + trackedSDKErrors[sdkErrorKind] = 1 + } + } + + /// Signals that the session was stopped with `stopSession()` API. + func trackWasStopped() { + wasStopped = true + } + + // MARK: - Exporting Attributes + + /// Set of quality and diagnostic attributes for the Session Ended metric. + internal struct Attributes: Encodable { + /// The type of OS component where the session was tracked. + let processType: String + /// The precondition that led to the creation of this session. + /// + /// Note: We don't expect it to ever become `nil`, but optionality is enforced in upstream code. + let precondition: String? + /// The session's duration, calculated from view events. + /// + /// This calculation only includes view events that are written to disk, with no consideration if the I/O operation + /// has succeeded or not. Views dropped through the mapper API are not included in this duration. + /// + /// Note: It becomes `nil` if no views were tracked in this session. + let duration: Int64? + /// Indicates if the session was stopped through `stopSession()` API. + let wasStopped: Bool + + struct ViewsCount: Encodable { + /// The number of distinct views (view UUIDs) sent during this session. + let total: Int + /// The number of standard "Background" views tracked during this session. + let background: Int + /// The number of standard "ApplicationLaunch" views tracked during this session (sanity check: we expect `0` or `1`). + let applicationLaunch: Int + + enum CodingKeys: String, CodingKey { + case total + case background + case applicationLaunch = "app_launch" + } + } + + let viewsCount: ViewsCount + + struct SDKErrorsCount: Encodable { + /// The total number of SDK errors that occurred during the session, excluding any effects from telemetry limits + /// such as duplicate filtering or maximum caps. + let total: Int + /// The map of TOP 5 SDK error kinds to the number of their occurrences during the session. + /// Error kinds may include characters illegal for being a JSON key, so they are escaped. + let byKind: [String: Int] + + enum CodingKeys: String, CodingKey { + case total + case byKind = "by_kind" + } + } + + let sdkErrorsCount: SDKErrorsCount + + enum CodingKeys: String, CodingKey { + case processType = "process_type" + case precondition + case duration + case wasStopped = "was_stopped" + case viewsCount = "views_count" + case sdkErrorsCount = "sdk_errors_count" + } + } + + /// Exports metric attributes for `Telemetry.metric(name:attributes:)`. + func asMetricAttributes() -> [String: Encodable] { + // Compute duration + var durationNs: Int64? + if let firstView = firstTrackedView, let lastView = lastTrackedView { + let endOfLastViewNs = lastView.startMs.msToNs.addingReportingOverflow(lastView.durationNs).partialValue + durationNs = endOfLastViewNs.subtractingReportingOverflow(firstView.startMs.msToNs).partialValue + } + + // Compute views count + let totalViewsCount = trackedViews.count + let backgroundViewsCount = trackedViews.values.filter({ $0.viewURL == RUMOffViewEventsHandlingRule.Constants.backgroundViewURL }).count + let appLaunchViewsCount = trackedViews.values.filter({ $0.viewURL == RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL }).count + + // Compute SDK errors count + let totalSDKErrors = trackedSDKErrors.count + let top5SDKErrorsByKind = top5SDKErrorsByKind(from: trackedSDKErrors) + + return [ + SDKMetricFields.typeKey: Constants.typeValue, + Constants.sessionIDKey: sessionID, + Constants.rseKey: Attributes( + processType: { + switch bundleType { + case .iOSApp: return "app" + case .iOSAppExtension: return "extension" + } + }(), + precondition: precondition?.rawValue, + duration: durationNs, + wasStopped: wasStopped, + viewsCount: .init( + total: totalViewsCount, + background: backgroundViewsCount, + applicationLaunch: appLaunchViewsCount + ), + sdkErrorsCount: .init( + total: totalSDKErrors, + byKind: top5SDKErrorsByKind + ) + ) + ] + } + + /// Returns the top 5 SDK errors with escaping their error kind. + /// - Parameter sdkErrors: All SDK errors. + /// - Returns: Top 5 errors with their count. + private func top5SDKErrorsByKind(from sdkErrors: [String: Int]) -> [String: Int] { + /// Replaces all non-alpanumeric characters with `_` (underscore). + func escapeNonAlphanumericCharacters(_ string: String) -> String { + let escaped = string.unicodeScalars.map { CharacterSet.alphanumerics.contains($0) ? Character($0) : "_" } + return String(escaped) + } + + let sortedEntries = sdkErrors.sorted { $0.value > $1.value } + let top5Entries = sortedEntries.prefix(5) + var top5: [String: Int] = [:] + for (key, value) in top5Entries { + top5[escapeNonAlphanumericCharacters(key)] = value + } + return top5 + } +} + +// MARK: - Helpers + +private extension Int64 { + /// Converts timestamp represented in milliseconds to nanoseconds with preventing Int64 overflow. + var msToNs: Int64 { multipliedReportingOverflow(by: 1_000_000).partialValue } +} diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift new file mode 100644 index 0000000000..0bdd7051ee --- /dev/null +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift @@ -0,0 +1,53 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal + +/// A controller responsible for managing "RUM Session Ended" metrics. +internal final class SessionEndedMetricController { + /// Dictionary to keep track of pending metrics, keyed by session ID. + @ReadWriteLock + private var pendingMetrics: [String: SessionEndedMetric] = [:] + + /// Telemetry endpoint for sending metrics. + private let telemetry: Telemetry + + /// Initializes a new instance of the metric controller. + /// - Parameter telemetry: The telemetry endpoint used for sending metrics. + init(telemetry: Telemetry) { + self.telemetry = telemetry + } + + /// Starts a new metric for a given session. + /// - Parameters: + /// - sessionID: The ID of the session to track. + /// - precondition: The precondition that led to starting this session. + /// - context: The SDK context at the moment of starting this session. + /// - Returns: The newly created `SessionEndedMetric` instance. + func startMetric(sessionID: String, precondition: RUMSessionPrecondition?, context: DatadogContext) -> SessionEndedMetric { + let metric = SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context) + pendingMetrics[sessionID] = metric + return metric + } + + /// Retrieves the metric for a given session ID. + /// - Parameter sessionID: The ID of the session to retrieve the metric for. + /// - Returns: The `SessionEndedMetric` instance if found, otherwise `nil`. + func metric(for sessionID: String) -> SessionEndedMetric? { + return pendingMetrics[sessionID] + } + + /// Ends the metric for a given session, sending it to telemetry and removing it from pending metrics. + /// - Parameter sessionID: The ID of the session to end the metric for. + func endMetric(sessionID: String) { + guard let metric = pendingMetrics[sessionID] else { + return + } + telemetry.metric(name: SessionEndedMetric.Constants.name, attributes: metric.asMetricAttributes()) + pendingMetrics[sessionID] = nil + } +} diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index ab08fe5a1b..e21f7405ec 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -131,8 +131,12 @@ extension RUMViewEvent: RandomMockable { /// Produces random `RUMViewEvent` with setting given fields to certain values. static func mockRandomWith( + sessionID: String = .mockRandom(), + viewID: String = .mockRandom(), + date: Int64 = .mockRandom(), viewIsActive: Bool? = .random(), viewTimeSpent: Int64 = .mockRandom(), + viewURL: String = .mockRandom(), crashCount: Int64? = nil ) -> RUMViewEvent { return RUMViewEvent( @@ -154,7 +158,7 @@ extension RUMViewEvent: RandomMockable { connectivity: .mockRandom(), container: nil, context: .mockRandom(), - date: .mockRandom(), + date: date, device: .mockRandom(), display: nil, os: .mockRandom(), @@ -162,7 +166,7 @@ extension RUMViewEvent: RandomMockable { service: .mockRandom(), session: .init( hasReplay: nil, - id: .mockRandom(), + id: sessionID, isActive: true, sampledForReplay: nil, type: .user @@ -192,7 +196,7 @@ extension RUMViewEvent: RandomMockable { flutterRasterTime: nil, frozenFrame: .init(count: .mockRandom()), frustration: nil, - id: .mockRandom(), + id: viewID, inForegroundPeriods: [ .init( duration: .mockRandom(), @@ -218,7 +222,7 @@ extension RUMViewEvent: RandomMockable { refreshRateMin: .mockRandom(), resource: .init(count: .mockRandom()), timeSpent: viewTimeSpent, - url: .mockRandom() + url: viewURL ) ) } diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift new file mode 100644 index 0000000000..a719e0e199 --- /dev/null +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift @@ -0,0 +1,356 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import TestUtilities +import DatadogInternal +@testable import DatadogRUM + +class SessionEndedMetricTests: XCTestCase { + private typealias Constants = SessionEndedMetric.Constants + private typealias SessionEndedAttributes = SessionEndedMetric.Attributes + private let sessionID: String = .mockRandom() + + func testReportingEmptyMetric() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertNil(rse.duration) + XCTAssertEqual(rse.viewsCount.total, 0) + XCTAssertEqual(rse.viewsCount.background, 0) + XCTAssertEqual(rse.viewsCount.applicationLaunch, 0) + XCTAssertEqual(rse.sdkErrorsCount.total, 0) + XCTAssertEqual(rse.sdkErrorsCount.byKind, [:]) + } + + // MARK: - Metric Type + + func testReportingMetricType() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + let attributes = metric.asMetricAttributes() + + // Then + XCTAssertEqual(attributes[SDKMetricFields.typeKey] as? String, Constants.typeValue) + } + + // MARK: - Session ID + + func testReportingSessionID() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + let attributes = metric.asMetricAttributes() + + // Then + XCTAssertEqual(attributes[Constants.sessionIDKey] as? String, sessionID) + } + + // MARK: - Process Type + + func testReportingAppProcessType() throws { + // Given + let metric = SessionEndedMetric( + sessionID: sessionID, precondition: .mockRandom(), context: .mockWith(applicationBundleType: .iOSApp) + ) + + // When + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.processType, "app") + } + + func testReportingExtensionProcessType() throws { + // Given + let metric = SessionEndedMetric( + sessionID: sessionID, precondition: .mockRandom(), context: .mockWith(applicationBundleType: .iOSAppExtension) + ) + + // When + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.processType, "extension") + } + + // MARK: - Precondition + + func testReportingSessionPrecondition() throws { + // Given + let expectedPrecondition: RUMSessionPrecondition = .mockRandom() + let metric = SessionEndedMetric( + sessionID: sessionID, precondition: expectedPrecondition, context: .mockRandom() + ) + + // When + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.precondition, expectedPrecondition.rawValue) + } + + // MARK: - Duration + + func testComputingDurationFromSingleView() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + let view: RUMViewEvent = .mockRandomWith(sessionID: sessionID) + + // When + metric.track(view: view) + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.duration, view.view.timeSpent) + } + + func testComputingDurationFromMultipleViews() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + let view1: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms, viewTimeSpent: 10.s2ns) + let view2: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms + 10.s2ms, viewTimeSpent: 20.s2ns) + let view3: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms + 10.s2ms + 20.s2ms, viewTimeSpent: 50.s2ns) + + // When + metric.track(view: view1) + metric.track(view: view2) + metric.track(view: view3) + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.duration, 10.s2ns + 20.s2ns + 50.s2ns) + } + + func testComputingDurationFromOverlappingViews() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + let view1: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms, viewTimeSpent: 10.s2ns) + let view2: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 15.s2ms, viewTimeSpent: 20.s2ns) // starts in the middle of `view1` + + // When + metric.track(view: view1) + metric.track(view: view2) + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.duration, 25.s2ns) + } + + func testDurationIsAlwaysComputedFromTheFirstAndLastView() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + let firstView: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 5.s2ms, viewTimeSpent: 10.s2ns) + let lastView: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 5.s2ms + 10.s2ms, viewTimeSpent: 20.s2ns) + + // When + metric.track(view: firstView) + (0..<10).forEach { _ in metric.track(view: .mockRandom()) } // middle views should not alter the duration + metric.track(view: lastView) + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.duration, 10.s2ns + 20.s2ns) + } + + func testWhenComputingDuration_itIgnoresViewsFromDifferentSession() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + metric.track(view: .mockRandom()) + metric.track(view: .mockRandom()) + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertNil(rse.duration) + } + + // MARK: - Was Stopped + + func testReportingSessionThatWasStopped() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + metric.trackWasStopped() + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertTrue(rse.wasStopped) + } + + func testReportingSessionThatWasNotStopped() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertFalse(rse.wasStopped) + } + + // MARK: - Views Count + + func testReportingTotalViewsCount() throws { + let viewIDs: Set = .mockRandom(count: .mockRandom(min: 1, max: 10)) + + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + viewIDs.forEach { metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: $0)) } + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.viewsCount.total, viewIDs.count) + } + + func testReportingBackgorundViewsCount() throws { + let backgroundViewIDs: Set = .mockRandom(count: .mockRandom(min: 1, max: 10)) + let otherViewIDs: Set = .mockRandom(count: .mockRandom(min: 1, max: 10)) + let viewIDs = backgroundViewIDs.union(otherViewIDs) + + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + viewIDs.forEach { viewID in + let viewURL = backgroundViewIDs.contains(viewID) ? RUMOffViewEventsHandlingRule.Constants.backgroundViewURL : .mockRandom() + metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: viewID, viewURL: viewURL)) + } + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.viewsCount.background, backgroundViewIDs.count) + } + + func testReportingApplicationLaunchViewsCount() throws { + let appLaunchViewIDs: Set = .mockRandom(count: .mockRandom(min: 1, max: 10)) + let otherViewIDs: Set = .mockRandom(count: .mockRandom(min: 1, max: 10)) + let viewIDs = appLaunchViewIDs.union(otherViewIDs) + + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + viewIDs.forEach { viewID in + let viewURL = appLaunchViewIDs.contains(viewID) ? RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL : .mockRandom() + metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: viewID, viewURL: viewURL)) + } + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.viewsCount.applicationLaunch, appLaunchViewIDs.count) + } + + func testReportingViewsCount_itIgnoresViewsFromDifferentSession() throws { + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + metric.track(view: .mockRandom()) + metric.track(view: .mockRandom()) + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.viewsCount.total, 0) + } + + // MARK: - SDK Errors Count + + func testReportingTotalSDKErrorsCount() throws { + let errorKinds: [String] = .mockRandom(count: .mockRandom(min: 0, max: 50)) + + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + errorKinds.forEach { metric.track(sdkErrorKind: $0) } + let attributes = metric.asMetricAttributes() + + // Then + let rse = try XCTUnwrap(attributes[Constants.rseKey] as? SessionEndedAttributes) + XCTAssertEqual(rse.sdkErrorsCount.total, errorKinds.count) + } + + func testReportingTopSDKErrorsCount() throws { + let errorKinds: [String: Int] = [ + "top1": 9, "top2": 8, "top3": 7, "top4": 6, + "top5": 5, "top6": 4, "top7": 3, "top8": 2, + ] + + // Given + let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + errorKinds.forEach { kind, count in + (0.. Date: Mon, 27 May 2024 18:51:05 +0200 Subject: [PATCH 42/71] RUM-1660 Inject Session Ended Metric into RUM --- .../Tests/Datadog/Mocks/RUMFeatureMocks.swift | 17 ++++++++++------ DatadogRUM/Sources/Feature/RUMFeature.swift | 4 +++- .../Scopes/RUMApplicationScope.swift | 6 +++--- .../Scopes/RUMScopeDependencies.swift | 5 ++++- .../RUMMonitor/Scopes/RUMSessionScope.swift | 17 +++++++++++----- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 20 +++++++++++-------- .../Scopes/RUMSessionScopeTests.swift | 16 +++++++++++---- 7 files changed, 57 insertions(+), 28 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift index 68e8750259..9642415537 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift @@ -725,7 +725,8 @@ extension RUMScopeDependencies { vitalsReaders: VitalsReaders? = nil, onSessionStart: @escaping RUM.SessionListener = mockNoOpSessionListener(), viewCache: ViewCache = ViewCache(), - fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock() + fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock(), + sessionEndedMetric: SessionEndedMetricController = SessionEndedMetricController(telemetry: NOPTelemetry()) ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: featureScope, @@ -742,7 +743,8 @@ extension RUMScopeDependencies { vitalsReaders: vitalsReaders, onSessionStart: onSessionStart, viewCache: viewCache, - fatalErrorContext: fatalErrorContext + fatalErrorContext: fatalErrorContext, + sessionEndedMetric: sessionEndedMetric ) } @@ -761,7 +763,8 @@ extension RUMScopeDependencies { vitalsReaders: VitalsReaders? = nil, onSessionStart: RUM.SessionListener? = nil, viewCache: ViewCache? = nil, - fatalErrorContext: FatalErrorContextNotifying? = nil + fatalErrorContext: FatalErrorContextNotifying? = nil, + sessionEndedMetric: SessionEndedMetricController? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: self.featureScope, @@ -778,7 +781,8 @@ extension RUMScopeDependencies { vitalsReaders: vitalsReaders ?? self.vitalsReaders, onSessionStart: onSessionStart ?? self.onSessionStart, viewCache: viewCache ?? self.viewCache, - fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext + fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext, + sessionEndedMetric: sessionEndedMetric ?? self.sessionEndedMetric ) } } @@ -800,6 +804,7 @@ extension RUMSessionScope { parent: RUMContextProvider = RUMContextProviderMock(), startTime: Date = .mockAny(), startPrecondition: RUMSessionPrecondition? = .userAppLaunch, + context: DatadogContext = .mockAny(), dependencies: RUMScopeDependencies = .mockAny(), hasReplay: Bool? = .mockAny() ) -> RUMSessionScope { @@ -808,8 +813,8 @@ extension RUMSessionScope { parent: parent, startTime: startTime, startPrecondition: startPrecondition, - dependencies: dependencies, - hasReplay: hasReplay + context: context, + dependencies: dependencies ) } } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 09da91f3bb..f088d2ff93 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -35,6 +35,7 @@ internal final class RUMFeature: DatadogRemoteFeature { ) let featureScope = core.scope(for: RUMFeature.self) + let sessionEndedMetric = SessionEndedMetricController(telemetry: core.telemetry) let dependencies = RUMScopeDependencies( featureScope: featureScope, rumApplicationID: configuration.applicationID, @@ -72,7 +73,8 @@ internal final class RUMFeature: DatadogRemoteFeature { }, onSessionStart: configuration.onSessionStart, viewCache: ViewCache(), - fatalErrorContext: FatalErrorContextNotifier(messageBus: featureScope) + fatalErrorContext: FatalErrorContextNotifier(messageBus: featureScope), + sessionEndedMetric: sessionEndedMetric ) self.monitor = Monitor( diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index 5279ad4711..b501484797 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -156,8 +156,8 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { parent: self, startTime: context.sdkInitDate, startPrecondition: startPrecondition, - dependencies: dependencies, - hasReplay: context.hasReplay + context: context, + dependencies: dependencies ) lastSessionEndReason = nil @@ -210,8 +210,8 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { parent: self, startTime: command.time, startPrecondition: startPrecondition, + context: context, dependencies: dependencies, - hasReplay: context.hasReplay, resumingViewScope: resumingViewScope ) lastActiveView = nil diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift index d325ac39ec..b40734c1f1 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMScopeDependencies.swift @@ -48,6 +48,7 @@ internal struct RUMScopeDependencies { /// Telemetry endpoint. let telemetry: Telemetry let sessionType: RUMSessionType + let sessionEndedMetric: SessionEndedMetricController init( featureScope: FeatureScope, @@ -64,7 +65,8 @@ internal struct RUMScopeDependencies { vitalsReaders: VitalsReaders?, onSessionStart: RUM.SessionListener?, viewCache: ViewCache, - fatalErrorContext: FatalErrorContextNotifying + fatalErrorContext: FatalErrorContextNotifying, + sessionEndedMetric: SessionEndedMetricController ) { self.featureScope = featureScope self.rumApplicationID = rumApplicationID @@ -82,6 +84,7 @@ internal struct RUMScopeDependencies { self.viewCache = viewCache self.fatalErrorContext = fatalErrorContext self.telemetry = featureScope.telemetry + self.sessionEndedMetric = sessionEndedMetric if ciTest != nil { self.sessionType = .ciTest diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index e46c343ff4..4748934ae3 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -75,14 +75,16 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { private var lastInteractionTime: Date /// The reason why this session has ended or `nil` if it is still active. private(set) var endReason: EndReason? + /// SDK metric that tracks the state of this session. + private let sessionEndedMetric: SessionEndedMetric init( isInitialSession: Bool, parent: RUMContextProvider, startTime: Date, startPrecondition: RUMSessionPrecondition?, + context: DatadogContext, dependencies: RUMScopeDependencies, - hasReplay: Bool?, resumingViewScope: RUMViewScope? = nil ) { self.parent = parent @@ -99,7 +101,12 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { sessionUUID: sessionUUID.rawValue, isInitialSession: isInitialSession, hasTrackedAnyView: false, - didStartWithReplay: hasReplay + didStartWithReplay: context.hasReplay + ) + self.sessionEndedMetric = dependencies.sessionEndedMetric.startMetric( + sessionID: sessionUUID.toRUMDataFormat, + precondition: startPrecondition, + context: context ) if let viewScope = resumingViewScope { @@ -113,7 +120,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { customTimings: [:], startTime: startTime, serverTimeOffset: viewScope.serverTimeOffset, - hasReplay: hasReplay + hasReplay: context.hasReplay ) } @@ -133,8 +140,8 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { parent: expiredSession.parent, startTime: startTime, startPrecondition: startPrecondition, - dependencies: expiredSession.dependencies, - hasReplay: context.hasReplay + context: context, + dependencies: expiredSession.dependencies ) // Transfer active Views by creating new `RUMViewScopes` for their identity objects: diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index 7266735fe3..f96ca1021d 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -771,7 +771,8 @@ extension RUMScopeDependencies { vitalsReaders: VitalsReaders? = nil, onSessionStart: @escaping RUM.SessionListener = mockNoOpSessionListener(), viewCache: ViewCache = ViewCache(), - fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock() + fatalErrorContext: FatalErrorContextNotifying = FatalErrorContextNotifierMock(), + sessionEndedMetric: SessionEndedMetricController = SessionEndedMetricController(telemetry: NOPTelemetry()) ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: featureScope, @@ -788,7 +789,8 @@ extension RUMScopeDependencies { vitalsReaders: vitalsReaders, onSessionStart: onSessionStart, viewCache: viewCache, - fatalErrorContext: fatalErrorContext + fatalErrorContext: fatalErrorContext, + sessionEndedMetric: sessionEndedMetric ) } @@ -807,7 +809,8 @@ extension RUMScopeDependencies { vitalsReaders: VitalsReaders? = nil, onSessionStart: RUM.SessionListener? = nil, viewCache: ViewCache? = nil, - fatalErrorContext: FatalErrorContextNotifying? = nil + fatalErrorContext: FatalErrorContextNotifying? = nil, + sessionEndedMetric: SessionEndedMetricController? = nil ) -> RUMScopeDependencies { return RUMScopeDependencies( featureScope: self.featureScope, @@ -824,7 +827,8 @@ extension RUMScopeDependencies { vitalsReaders: vitalsReaders ?? self.vitalsReaders, onSessionStart: onSessionStart ?? self.onSessionStart, viewCache: viewCache ?? self.viewCache, - fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext + fatalErrorContext: fatalErrorContext ?? self.fatalErrorContext, + sessionEndedMetric: sessionEndedMetric ?? self.sessionEndedMetric ) } } @@ -846,16 +850,16 @@ extension RUMSessionScope { parent: RUMContextProvider = RUMContextProviderMock(), startTime: Date = .mockAny(), startPrecondition: RUMSessionPrecondition? = .userAppLaunch, - dependencies: RUMScopeDependencies = .mockAny(), - hasReplay: Bool? = .mockAny() + context: DatadogContext = .mockAny(), + dependencies: RUMScopeDependencies = .mockAny() ) -> RUMSessionScope { return RUMSessionScope( isInitialSession: isInitialSession, parent: parent, startTime: startTime, startPrecondition: startPrecondition, - dependencies: dependencies, - hasReplay: hasReplay + context: context, + dependencies: dependencies ) } } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift index fef13b0671..4c63c5d0d1 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMSessionScopeTests.swift @@ -348,12 +348,16 @@ class RUMSessionScopeTests: XCTestCase { let scope: RUMSessionScope = .mockWith( isInitialSession: randomIsInitialSession, parent: parent, + context: .mockWith( + baggages: [ + SessionReplayDependency.hasReplay: FeatureBaggage(randomIsReplayBeingRecorded), + ] + ), dependencies: .mockWith( featureScope: featureScope, sessionSampler: Bool.random() ? .mockKeepAll() : .mockRejectAll(), // no matter if sampled or not, fatalErrorContext: fatalErrorContext - ), - hasReplay: randomIsReplayBeingRecorded + ) ) // Then @@ -379,11 +383,15 @@ class RUMSessionScopeTests: XCTestCase { isInitialSession: randomIsInitialSession, parent: parent, startTime: sessionStartTime, + context: .mockWith( + baggages: [ + SessionReplayDependency.hasReplay: FeatureBaggage(randomIsReplayBeingRecorded), + ] + ), dependencies: .mockWith( featureScope: featureScope, fatalErrorContext: fatalErrorContext - ), - hasReplay: randomIsReplayBeingRecorded + ) ) // When From e90f1492dc8b4e431bf1c5dd98b340a1ecfd0b59 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 28 May 2024 10:51:15 +0200 Subject: [PATCH 43/71] RUM-1660 Add tests for Session Ended Metric Controller --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 + .../SDKMetrics/SessionEndedMetric.swift | 10 +- .../SessionEndedMetricController.swift | 26 ++-- .../SessionEndedMetricControllerTests.swift | 119 ++++++++++++++++++ .../SDKMetrics/SessionEndedMetricTests.swift | 11 +- 5 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 5ea418df5c..e875d1ccb5 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -592,6 +592,8 @@ 61DA8CB828647A500074A606 /* InternalLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA8CB728647A500074A606 /* InternalLoggerTests.swift */; }; 61DA8CB928647A500074A606 /* InternalLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DA8CB728647A500074A606 /* InternalLoggerTests.swift */; }; 61DB33B225DEDFC200F7EA71 /* CustomObjcViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */; }; + 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; + 61DCC8482C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; 61E45BE724519A3700F2C652 /* JSONDataMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45BE624519A3700F2C652 /* JSONDataMatcher.swift */; }; 61E45ED12451A8730061DAC7 /* SpanMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45ED02451A8730061DAC7 /* SpanMatcher.swift */; }; 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */; }; @@ -2586,6 +2588,7 @@ 61DA8CB728647A500074A606 /* InternalLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalLoggerTests.swift; sourceTree = ""; }; 61DB33B025DEDFC200F7EA71 /* CustomObjcViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomObjcViewController.h; sourceTree = ""; }; 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomObjcViewController.m; sourceTree = ""; }; + 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricControllerTests.swift; sourceTree = ""; }; 61DE333525C8278A008E3EC2 /* CrashReportingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingPlugin.swift; sourceTree = ""; }; 61E45BCE2450A6EC00F2C652 /* TraceIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceIDTests.swift; sourceTree = ""; }; 61E45BD12450F65B00F2C652 /* SpanEventBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanEventBuilderTests.swift; sourceTree = ""; }; @@ -4840,6 +4843,7 @@ isa = PBXGroup; children = ( 6174D6192BFE449300EC7469 /* SessionEndedMetricTests.swift */, + 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */, ); path = SDKMetrics; sourceTree = ""; @@ -8669,6 +8673,7 @@ buildActionMask = 2147483647; files = ( 6188697D2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */, + 61DCC8482C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */, D23F8EA029DDCD38001CFAE8 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, D23F8EA229DDCD38001CFAE8 /* RUMSessionScopeTests.swift in Sources */, D23F8EA329DDCD38001CFAE8 /* RUMUserActionScopeTests.swift in Sources */, @@ -8986,6 +8991,7 @@ buildActionMask = 2147483647; files = ( 6188697C2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */, + 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */, D29A9FA629DDB483005C54A4 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, D29A9FBD29DDB483005C54A4 /* RUMSessionScopeTests.swift in Sources */, D29A9FAB29DDB483005C54A4 /* RUMUserActionScopeTests.swift in Sources */, diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift index 66b276f950..aec4077bd4 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift @@ -18,12 +18,10 @@ internal final class SessionEndedMetric { static let typeValue = "rum session ended" /// Namespace for bundling metric attributes ("rse" = "RUM Session Ended"). static let rseKey = "rse" - /// Key referencing the session ID (`String`) that the metric refers to. - static let sessionIDKey = "sessionID" } /// An ID of the session being tracked through this metric object. - private let sessionID: String + let sessionID: String /// The type of OS component where the session was tracked. private let bundleType: BundleType @@ -128,7 +126,7 @@ internal final class SessionEndedMetric { /// /// Note: We don't expect it to ever become `nil`, but optionality is enforced in upstream code. let precondition: String? - /// The session's duration, calculated from view events. + /// The session's duration (in nanoseconds), calculated from view events. /// /// This calculation only includes view events that are written to disk, with no consideration if the I/O operation /// has succeeded or not. Views dropped through the mapper API are not included in this duration. @@ -196,12 +194,12 @@ internal final class SessionEndedMetric { let appLaunchViewsCount = trackedViews.values.filter({ $0.viewURL == RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL }).count // Compute SDK errors count - let totalSDKErrors = trackedSDKErrors.count + let totalSDKErrors = trackedSDKErrors.values.reduce(0, +) let top5SDKErrorsByKind = top5SDKErrorsByKind(from: trackedSDKErrors) return [ SDKMetricFields.typeKey: Constants.typeValue, - Constants.sessionIDKey: sessionID, + SDKMetricFields.sessionIDOverrideKey: sessionID, Constants.rseKey: Attributes( processType: { switch bundleType { diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift index 0bdd7051ee..65bc7edde0 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift @@ -11,7 +11,10 @@ import DatadogInternal internal final class SessionEndedMetricController { /// Dictionary to keep track of pending metrics, keyed by session ID. @ReadWriteLock - private var pendingMetrics: [String: SessionEndedMetric] = [:] + private var metricsBySessionID: [String: SessionEndedMetric] = [:] + /// Array to keep track of pending metrics in their start order. + @ReadWriteLock + private var metrics: [SessionEndedMetric] = [] /// Telemetry endpoint for sending metrics. private let telemetry: Telemetry @@ -28,26 +31,35 @@ internal final class SessionEndedMetricController { /// - precondition: The precondition that led to starting this session. /// - context: The SDK context at the moment of starting this session. /// - Returns: The newly created `SessionEndedMetric` instance. - func startMetric(sessionID: String, precondition: RUMSessionPrecondition?, context: DatadogContext) -> SessionEndedMetric { + func startMetric(sessionID: String, precondition: RUMSessionPrecondition?, context: DatadogContext) { + guard sessionID != RUMUUID.nullUUID.toRUMDataFormat else { + return // do not track metric when session is not sampled + } let metric = SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context) - pendingMetrics[sessionID] = metric - return metric + metricsBySessionID[sessionID] = metric + metrics.append(metric) } /// Retrieves the metric for a given session ID. /// - Parameter sessionID: The ID of the session to retrieve the metric for. /// - Returns: The `SessionEndedMetric` instance if found, otherwise `nil`. func metric(for sessionID: String) -> SessionEndedMetric? { - return pendingMetrics[sessionID] + return metricsBySessionID[sessionID] + } + + /// Retrieves the last started metric. + var latestMetric: SessionEndedMetric? { + return metrics.last } /// Ends the metric for a given session, sending it to telemetry and removing it from pending metrics. /// - Parameter sessionID: The ID of the session to end the metric for. func endMetric(sessionID: String) { - guard let metric = pendingMetrics[sessionID] else { + guard let metric = metricsBySessionID[sessionID] else { return } telemetry.metric(name: SessionEndedMetric.Constants.name, attributes: metric.asMetricAttributes()) - pendingMetrics[sessionID] = nil + metricsBySessionID[sessionID] = nil + metrics.removeAll(where: { $0 === metric }) // O(n), but "ending the metric" is very rare event } } diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift new file mode 100644 index 0000000000..57c7bb1952 --- /dev/null +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift @@ -0,0 +1,119 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import TestUtilities +import DatadogInternal +@testable import DatadogRUM + +class SessionEndedMetricControllerTests: XCTestCase { + private let telemetry = TelemetryMock() + + func testWhenMetricIsStarted_itCanBeRetrievedByID() throws { + let controller = SessionEndedMetricController(telemetry: telemetry) + + // When + let sessionID1: String = .mockRandom() + let sessionID2: String = .mockRandom() + controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom()) + controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom()) + + // Then + let metric1 = try XCTUnwrap(controller.metric(for: sessionID1)) + let metric2 = try XCTUnwrap(controller.metric(for: sessionID2)) + XCTAssertEqual(metric1.sessionID, sessionID1) + XCTAssertEqual(metric2.sessionID, sessionID2) + } + + func testWhenMetricIsStarted_itCanBeRetrievedAsLatest() throws { + let controller = SessionEndedMetricController(telemetry: telemetry) + + // When + let sessionID1: String = .mockRandom() + let sessionID2: String = .mockRandom() + controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom()) + controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom()) + + // Then + XCTAssertEqual(controller.latestMetric?.sessionID, sessionID2) + controller.endMetric(sessionID: sessionID2) + XCTAssertEqual(controller.latestMetric?.sessionID, sessionID1) + controller.endMetric(sessionID: sessionID1) + XCTAssertNil(controller.latestMetric) + } + + func testWhenMetricIsEnded_itIsSentToTelemetry() throws { + let sessionID: String = .mockRandom() + let controller = SessionEndedMetricController(telemetry: telemetry) + controller.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + controller.endMetric(sessionID: sessionID) + + // Then + let metric = try XCTUnwrap(telemetry.messages.firstMetric(named: SessionEndedMetric.Constants.name)) + XCTAssertEqual(metric.attributes[SDKMetricFields.typeKey] as? String, SessionEndedMetric.Constants.typeValue) + XCTAssertEqual(metric.attributes[SDKMetricFields.sessionIDOverrideKey] as? String, sessionID) + } + + func testAfterMetricIsEnded_itCanNoLongerBeRetrieved() throws { + let sessionID: String = .mockRandom() + let controller = SessionEndedMetricController(telemetry: telemetry) + controller.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + + // When + XCTAssertNotNil(controller.metric(for: sessionID)) + controller.endMetric(sessionID: sessionID) + + // Then + XCTAssertNil(controller.metric(for: sessionID)) + XCTAssertNil(controller.latestMetric) + } + + func testWhenSessionIsSampled_itDoesNotTrackMetric() throws { + let controller = SessionEndedMetricController(telemetry: telemetry) + + // When + let rejectedSessionID = RUMUUID.nullUUID.toRUMDataFormat + controller.startMetric(sessionID: rejectedSessionID, precondition: .mockRandom(), context: .mockRandom()) + + // Then + XCTAssertNil(controller.metric(for: rejectedSessionID)) + XCTAssertNil(controller.latestMetric) + + controller.endMetric(sessionID: rejectedSessionID) + XCTAssertTrue(telemetry.messages.isEmpty) + } + + // MARK: - Thread Safety + + func testTrackingSessionEndedMetricIsThreadSafe() { + let sessionIDs: [String] = .mockRandom(count: 10) + let controller = SessionEndedMetricController(telemetry: telemetry) + + // swiftlint:disable opening_brace + callConcurrently( + closures: [ + { controller.startMetric( + sessionID: sessionIDs.randomElement()!, precondition: .mockRandom(), context: .mockRandom() + ) }, + { _ = controller.metric(for: sessionIDs.randomElement()!) }, + { + _ = controller.metric(for: sessionIDs.randomElement()!)?.track(view: .mockRandom()) + }, + { + _ = controller.metric(for: sessionIDs.randomElement()!)?.track(sdkErrorKind: .mockRandom()) + }, + { + _ = controller.metric(for: sessionIDs.randomElement()!)?.trackWasStopped() + }, + { controller.endMetric(sessionID: sessionIDs.randomElement()!) }, + ], + iterations: 100 + ) + // swiftlint:enable opening_brace + } +} diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift index a719e0e199..1988097129 100644 --- a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift @@ -54,7 +54,7 @@ class SessionEndedMetricTests: XCTestCase { let attributes = metric.asMetricAttributes() // Then - XCTAssertEqual(attributes[Constants.sessionIDKey] as? String, sessionID) + XCTAssertEqual(attributes[SDKMetricFields.sessionIDOverrideKey] as? String, sessionID) } // MARK: - Process Type @@ -286,18 +286,21 @@ class SessionEndedMetricTests: XCTestCase { // MARK: - SDK Errors Count func testReportingTotalSDKErrorsCount() throws { - let errorKinds: [String] = .mockRandom(count: .mockRandom(min: 0, max: 50)) + let errorKinds: [String] = .mockRandom(count: .mockRandom(min: 0, max: 10)) + let repetitions = 5 // Given let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - errorKinds.forEach { metric.track(sdkErrorKind: $0) } + (0.. Date: Wed, 29 May 2024 11:50:42 +0200 Subject: [PATCH 44/71] RUM-1660 Track "RUM Session Ended" attributes in RUM --- Datadog/Datadog.xcodeproj/project.pbxproj | 20 ++ ...UMSessionEndedMetricIntegrationTests.swift | 231 ++++++++++++++++++ .../Tests/Datadog/Mocks/CoreMocks.swift | 10 - .../Sources/SDKMetrics/SDKMetricFields.swift | 8 +- DatadogRUM/Sources/Feature/RUMFeature.swift | 1 + .../Integrations/TelemetryInterecptor.swift | 31 +++ .../Integrations/TelemetryReceiver.swift | 7 +- DatadogRUM/Sources/RUMMonitor/RUMScope.swift | 10 + .../Scopes/RUMApplicationScope.swift | 5 + .../RUMMonitor/Scopes/RUMResourceScope.swift | 4 +- .../RUMMonitor/Scopes/RUMSessionScope.swift | 15 +- .../Scopes/RUMUserActionScope.swift | 4 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 8 +- .../Tests/RUMMonitor/RUMScopeTests.swift | 1 + 14 files changed, 334 insertions(+), 21 deletions(-) create mode 100644 Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift create mode 100644 DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index e875d1ccb5..efb8e74dc6 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -594,6 +594,10 @@ 61DB33B225DEDFC200F7EA71 /* CustomObjcViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */; }; 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; 61DCC8482C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; + 61DCC84A2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */; }; + 61DCC84B2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */; }; + 61DCC84E2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */; }; + 61DCC84F2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */; }; 61E45BE724519A3700F2C652 /* JSONDataMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45BE624519A3700F2C652 /* JSONDataMatcher.swift */; }; 61E45ED12451A8730061DAC7 /* SpanMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45ED02451A8730061DAC7 /* SpanMatcher.swift */; }; 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */; }; @@ -2589,6 +2593,8 @@ 61DB33B025DEDFC200F7EA71 /* CustomObjcViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomObjcViewController.h; sourceTree = ""; }; 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomObjcViewController.m; sourceTree = ""; }; 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricControllerTests.swift; sourceTree = ""; }; + 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionEndedMetricIntegrationTests.swift; sourceTree = ""; }; + 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterecptor.swift; sourceTree = ""; }; 61DE333525C8278A008E3EC2 /* CrashReportingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingPlugin.swift; sourceTree = ""; }; 61E45BCE2450A6EC00F2C652 /* TraceIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceIDTests.swift; sourceTree = ""; }; 61E45BD12450F65B00F2C652 /* SpanEventBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanEventBuilderTests.swift; sourceTree = ""; }; @@ -4635,6 +4641,7 @@ D236BE2729520FED00676E67 /* CrashReportReceiver.swift */, D215ED6A29D2E1080046B721 /* ErrorMessageReceiver.swift */, D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */, + 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */, ); path = Integrations; sourceTree = ""; @@ -5259,6 +5266,14 @@ path = NTP; sourceTree = ""; }; + 61DCC84C2C05D4E500CB59E5 /* SDKMetrics */ = { + isa = PBXGroup; + children = ( + 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */, + ); + path = SDKMetrics; + sourceTree = ""; + }; 61E45BD02450F64100F2C652 /* Span */ = { isa = PBXGroup; children = ( @@ -5317,6 +5332,7 @@ 61E8C5072B28898800E709B4 /* StartingRUMSessionTests.swift */, 6167E6DC2B811A8300C3CA2D /* AppHangsMonitoringTests.swift */, D2552AF42BBC47D900A45725 /* WebEventIntegrationTests.swift */, + 61DCC84C2C05D4E500CB59E5 /* SDKMetrics */, ); path = RUM; sourceTree = ""; @@ -8034,6 +8050,7 @@ D24C9C7129A7D57A002057CF /* DirectoriesMock.swift in Sources */, D22743E329DEB90B001A7EF9 /* RUMDebuggingTests.swift in Sources */, 614798992A459B2E0095CB02 /* DDTraceConfigurationTests.swift in Sources */, + 61DCC84A2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */, 61133C5F2423990D00786299 /* DataUploaderTests.swift in Sources */, D2A1EE36287EB8DB00D28DFB /* ServerOffsetPublisherTests.swift in Sources */, 61BBD19724ED50040023E65F /* DatadogConfigurationTests.swift in Sources */, @@ -8665,6 +8682,7 @@ D23F8E8D29DDCD28001CFAE8 /* VitalRefreshRateReader.swift in Sources */, D23F8E8E29DDCD28001CFAE8 /* UIKitRUMUserActionsHandler.swift in Sources */, D23F8E8F29DDCD28001CFAE8 /* RUMUUIDGenerator.swift in Sources */, + 61DCC84F2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8983,6 +9001,7 @@ D29A9F8929DD85BB005C54A4 /* VitalRefreshRateReader.swift in Sources */, D29A9F6929DD85BB005C54A4 /* UIKitRUMUserActionsHandler.swift in Sources */, D29A9F5229DD85BB005C54A4 /* RUMUUIDGenerator.swift in Sources */, + 61DCC84E2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9221,6 +9240,7 @@ A7EA11622AB0CE6C00C73970 /* DDUIKitRUMActionsPredicateTests.swift in Sources */, D2CB6EE527C520D400A62B57 /* DataUploadConditionsTests.swift in Sources */, D2CB6EE627C520D400A62B57 /* DateFormattingTests.swift in Sources */, + 61DCC84B2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */, D2CB6EE727C520D400A62B57 /* FileTests.swift in Sources */, 610ABD4D2A6930CA00AFEA34 /* CoreTelemetryIntegrationTests.swift in Sources */, D2CB6EEA27C520D400A62B57 /* LogMatcher.swift in Sources */, diff --git a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift new file mode 100644 index 0000000000..7fba479ce5 --- /dev/null +++ b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift @@ -0,0 +1,231 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import TestUtilities +@testable import DatadogRUM + +class RUMSessionEndedMetricIntegrationTests: XCTestCase { + private let dateProvider = DateProviderMock() + private var core: DatadogCoreProxy! // swiftlint:disable:this implicitly_unwrapped_optional + private var rumConfig: RUM.Configuration! // swiftlint:disable:this implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + core = DatadogCoreProxy() + core.context = .mockWith( + launchTime: .mockWith(launchDate: dateProvider.now), + applicationStateHistory: .mockAppInForeground(since: dateProvider.now) + ) + rumConfig = RUM.Configuration(applicationID: .mockAny()) + rumConfig.telemetrySampleRate = 100 + rumConfig.metricsTelemetrySampleRate = 100 + rumConfig.dateProvider = dateProvider + } + + override func tearDown() { + core.flushAndTearDown() + core = nil + rumConfig = nil + super.tearDown() + } + + // MARK: - Conditions For Sending The Metric + + func testWhenSessionEndsWithStopAPI() throws { + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + monitor.startView(key: "key", name: "View") + + // When + monitor.stopSession() + + // Then + let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + XCTAssertTrue(metricAttributes.wasStopped) + } + + func testWhenSessionEndsDueToInactivityTimeout() throws { + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + monitor.startView(key: "key1", name: "View1") + + // When + dateProvider.now += RUMSessionScope.Constants.sessionTimeoutDuration + 1.seconds + monitor.startView(key: "key2", name: "View2") + + // Then + let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + XCTAssertFalse(metricAttributes.wasStopped) + } + + func testWhenSessionReachesMaxDuration() throws { + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + monitor.startView(key: "key", name: "View") + + // When + let deadline = dateProvider.now + RUMSessionScope.Constants.sessionMaxDuration * 1.5 + while dateProvider.now < deadline { + monitor.addAction(type: .custom, name: "action") + dateProvider.now += RUMSessionScope.Constants.sessionTimeoutDuration - 1.seconds + } + + // Then + let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + XCTAssertFalse(metricAttributes.wasStopped) + } + + func testWhenSessionIsNotSampled_thenMetricIsNotSent() throws { + rumConfig.sessionSampleRate = 0 + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + monitor.startView(key: "key", name: "View") + + // When + monitor.stopSession() + + // Then + let events = core.waitAndReturnEventsData(ofFeature: RUMFeature.name, timeout: .now() + 0.5) + XCTAssertTrue(events.isEmpty) + } + + // MARK: - Reporting Session Attributes + + func testReportingSessionID() throws { + var currentSessionID: String? + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + monitor.startView(key: "key", name: "View") + monitor.currentSessionID { currentSessionID = $0 } + monitor.stopView(key: "key") + + // When + monitor.stopSession() + + // Then + let metric = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()) + let expectedSessionID = try XCTUnwrap(currentSessionID) + XCTAssertEqual(metric.session?.id, expectedSessionID.lowercased()) + } + + func testTrackingSessionDuration() throws { + let startTime = dateProvider.now + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + dateProvider.now += 5.seconds + monitor.startView(key: "key1", name: "View1") + dateProvider.now += 5.seconds + monitor.startView(key: "key2", name: "View2") + dateProvider.now += 5.seconds + monitor.startView(key: "key3", name: "View3") + dateProvider.now += 5.seconds + monitor.stopView(key: "key3") + + // When + monitor.stopSession() + + // Then + let expectedDuration = dateProvider.now.timeIntervalSince(startTime) + let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + XCTAssertEqual(metricAttributes.duration, expectedDuration.toInt64Nanoseconds) + } + + func testTrackingViewsCount() throws { + rumConfig.trackBackgroundEvents = true // enable tracking "Background" view + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + (0..<3).forEach { _ in + // Simulate app in foreground: + core.context = .mockWith(applicationStateHistory: .mockAppInForeground(since: dateProvider.now)) + + // Track 2 distinct views: + dateProvider.now += 5.seconds + monitor.startView(key: "key1", name: "View1") + dateProvider.now += 5.seconds + monitor.startView(key: "key2", name: "View2") + dateProvider.now += 5.seconds + monitor.stopView(key: "key2") + + // Simulate app in background: + core.context = .mockWith(applicationStateHistory: .mockAppInBackground(since: dateProvider.now)) + + // Track resource without view: + dateProvider.now += 1.seconds + monitor.startResource(resourceKey: "resource", url: .mockAny()) + dateProvider.now += 1.seconds + monitor.stopResource(resourceKey: "resource", response: .mockAny()) + } + + // When + monitor.stopSession() + + // Then + let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + XCTAssertEqual(metricAttributes.viewsCount.total, 10) + XCTAssertEqual(metricAttributes.viewsCount.applicationLaunch, 1) + XCTAssertEqual(metricAttributes.viewsCount.background, 3) + } + + func testTrackingSDKErrors() throws { + RUM.enable(with: rumConfig, in: core) + + // Given + let monitor = RUMMonitor.shared(in: core) + monitor.startView(key: "key", name: "View") + + core.flush() + (0..<9).forEach { _ in core.telemetry.error(id: "id1", message: .mockAny(), kind: "kind1", stack: .mockAny()) } + (0..<8).forEach { _ in core.telemetry.error(id: "id2", message: .mockAny(), kind: "kind2", stack: .mockAny()) } + (0..<7).forEach { _ in core.telemetry.error(id: "id3", message: .mockAny(), kind: "kind3", stack: .mockAny()) } + (0..<6).forEach { _ in core.telemetry.error(id: "id4", message: .mockAny(), kind: "kind4", stack: .mockAny()) } + (0..<5).forEach { _ in core.telemetry.error(id: "id5", message: .mockAny(), kind: "kind5", stack: .mockAny()) } + (0..<4).forEach { _ in core.telemetry.error(id: "id6", message: .mockAny(), kind: "kind6", stack: .mockAny()) } + core.flush() + + // When + monitor.stopSession() + + // Then + let metricAttributes = try XCTUnwrap(core.waitAndReturnSessionEndedMetricEvent()?.attributes) + XCTAssertEqual(metricAttributes.sdkErrorsCount.total, 39, "It should count all SDK errors") + XCTAssertEqual( + metricAttributes.sdkErrorsCount.byKind, + ["kind1": 9, "kind2": 8, "kind3": 7, "kind4": 6, "kind5": 5], + "It should report TOP 5 error kinds" + ) + } +} + +// MARK: - Helpers + +private extension DatadogCoreProxy { + func waitAndReturnSessionEndedMetricEvent() -> TelemetryDebugEvent? { + let events = waitAndReturnEvents(ofFeature: RUMFeature.name, ofType: TelemetryDebugEvent.self) + return events.first(where: { $0.telemetry.message == "[Mobile Metric] \(SessionEndedMetric.Constants.name)" }) + } +} + +private extension TelemetryDebugEvent { + var attributes: SessionEndedMetric.Attributes? { + return telemetry.telemetryInfo[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes + } +} + diff --git a/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift b/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift index d9aeb3fa17..6691eab176 100644 --- a/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/CoreMocks.swift @@ -188,16 +188,6 @@ extension UploadPerformanceMock { } } -extension BundleType: AnyMockable, RandomMockable { - public static func mockAny() -> BundleType { - return .iOSApp - } - - public static func mockRandom() -> BundleType { - return [.iOSApp, .iOSAppExtension].randomElement()! - } -} - extension PerformancePreset: AnyMockable, RandomMockable { public static func mockAny() -> Self { PerformancePreset(batchSize: .medium, uploadFrequency: .average, bundleType: .iOSApp) diff --git a/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift b/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift index b2b98b5470..5651cfa1c9 100644 --- a/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift +++ b/DatadogInternal/Sources/SDKMetrics/SDKMetricFields.swift @@ -8,6 +8,12 @@ import Foundation /// Common fields in SDK metrics. public enum SDKMetricFields { - /// Metric type key. + /// Metric type key. It expects `String` value. public static let typeKey = "metric_type" + + /// Key referencing the session ID (`String`) that the metric should be sent with. It expects `String` value. + /// + /// When attached to metric attributes, the value of this key (session ID) will be used to replace + /// the ID of session that the metric was collected in. The key itself is dropped before the metric is sent. + public static let sessionIDOverrideKey = "session_id_override" } diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index f088d2ff93..cd6903893f 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -100,6 +100,7 @@ internal final class RUMFeature: DatadogRemoteFeature { telemetry: core.telemetry ) self.messageReceiver = CombinedFeatureMessageReceiver( + TelemetryInterecptor(sessionEndedMetric: sessionEndedMetric), TelemetryReceiver( featureScope: featureScope, dateProvider: configuration.dateProvider, diff --git a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift b/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift new file mode 100644 index 0000000000..1c1a66b0f7 --- /dev/null +++ b/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift @@ -0,0 +1,31 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal + +internal struct TelemetryInterecptor: FeatureMessageReceiver { + let sessionEndedMetric: SessionEndedMetricController + + func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { + guard case .telemetry(let telemetry) = message else { + return false + } + + switch telemetry { + case .error(let id, let message, let kind, let stack): + interceptError(id: id, message: message, kind: kind, stack: stack) + default: + break + } + + return false // do not consume, pass to next receivers + } + + private func interceptError(id: String, message: String, kind: String, stack: String) { + sessionEndedMetric.latestMetric?.track(sdkErrorKind: kind) + } +} diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 1e90babbb3..eb0c159086 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -205,6 +205,11 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { record(event: nil) { context, writer in let rum = try? context.baggages[RUMFeature.name]?.decode(type: RUMCoreContext.self) + // Override sessionID using standard `SDKMetricFields`, otherwise use current RUM session ID: + var attributes = attributes + let sessionIDOverride = attributes.removeValue(forKey: SDKMetricFields.sessionIDOverrideKey) as? String + let sessionID = sessionIDOverride ?? rum?.sessionID + let event = TelemetryDebugEvent( dd: .init(), action: rum?.userActionID.map { .init(id: $0) }, @@ -212,7 +217,7 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { date: date.addingTimeInterval(context.serverTimeOffset).timeIntervalSince1970.toInt64Milliseconds, experimentalFeatures: nil, service: "dd-sdk-ios", - session: rum.map { .init(id: $0.sessionID) }, + session: sessionID.map { .init(id: $0) }, source: .init(rawValue: context.source) ?? .ios, telemetry: .init( message: "[Mobile Metric] \(name)", diff --git a/DatadogRUM/Sources/RUMMonitor/RUMScope.swift b/DatadogRUM/Sources/RUMMonitor/RUMScope.swift index 90fe7413b5..f7f077bcc5 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMScope.swift @@ -8,6 +8,9 @@ import Foundation import DatadogInternal internal protocol RUMScope: AnyObject { + /// Container bundling dependencies for this scope. + var dependencies: RUMScopeDependencies { get } + /// Processes given command. Returns: /// * `true` if the scope should be kept open. /// * `false` if the scope should be closed. @@ -23,6 +26,13 @@ extension RUMScope { } } +extension RUMScope where Self: RUMContextProvider { + /// The "RUM Session Ended" metric tracking the session that this `RUMScope` belongs to. + var sessionEndedMetric: SessionEndedMetric? { + dependencies.sessionEndedMetric.metric(for: context.sessionID.toRUMDataFormat) + } +} + extension Array where Element: RUMScope { /// Propagates given `command` through this array of scopes and manages their lifecycle by /// filtering scopes that get closed. diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index b501484797..3ac8ea74cf 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -30,6 +30,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { // MARK: - Initialization + /// Container bundling dependencies for this scope. let dependencies: RUMScopeDependencies init(dependencies: RUMScopeDependencies) { @@ -99,6 +100,10 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { return scope } + // proccss(command:context:writer) returned false, so the scope will be deallocated at the end of + // this execution context. End the "RUM Session Ended" metric: + defer { dependencies.sessionEndedMetric.endMetric(sessionID: scope.sessionUUID.toRUMDataFormat) } + // proccss(command:context:writer) returned false, but if the scope is still active // it means the session reached one of the end reasons guard let endReason = scope.endReason else { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index 41cd687c0d..1f410380e8 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -11,7 +11,9 @@ internal class RUMResourceScope: RUMScope { // MARK: - Initialization let context: RUMContext - private let dependencies: RUMScopeDependencies + + /// Container bundling dependencies for this scope. + let dependencies: RUMScopeDependencies /// This Resource's UUID. let resourceUUID: RUMUUID diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index 4748934ae3..0492f53868 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -53,10 +53,12 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // MARK: - Initialization unowned let parent: RUMContextProvider - private let dependencies: RUMScopeDependencies + + /// Container bundling dependencies for this scope. + let dependencies: RUMScopeDependencies /// Automatically detect background events by creating "Background" view if no other view is active - internal let trackBackgroundEvents: Bool + let trackBackgroundEvents: Bool /// This Session UUID. Equals `.nullUUID` if the Session is sampled. let sessionUUID: RUMUUID @@ -75,8 +77,6 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { private var lastInteractionTime: Date /// The reason why this session has ended or `nil` if it is still active. private(set) var endReason: EndReason? - /// SDK metric that tracks the state of this session. - private let sessionEndedMetric: SessionEndedMetric init( isInitialSession: Bool, @@ -103,7 +103,9 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { hasTrackedAnyView: false, didStartWithReplay: context.hasReplay ) - self.sessionEndedMetric = dependencies.sessionEndedMetric.startMetric( + + // Start tracking "RUM Session Ended" metric for this session + dependencies.sessionEndedMetric.startMetric( sessionID: sessionUUID.toRUMDataFormat, precondition: startPrecondition, context: context @@ -188,7 +190,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { } if !isSampled { - // Make sure sessions end even if they are sampled + // Make sure sessions end even if they are not sampled if command is RUMStopSessionCommand { endReason = .stopAPI return false // end this session (no longer keep the session scope) @@ -200,6 +202,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { var deactivating = false if isActive { if command is RUMStopSessionCommand { + sessionEndedMetric?.trackWasStopped() endReason = .stopAPI deactivating = true } else if let startApplicationCommand = command as? RUMApplicationStartCommand { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift index aafe5021a2..251fd661a4 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -19,7 +19,9 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { // MARK: - Initialization private unowned let parent: RUMContextProvider - private let dependencies: RUMScopeDependencies + + /// Container bundling dependencies for this scope. + let dependencies: RUMScopeDependencies /// The type of this User Action. internal let actionType: RUMActionType diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index bca5bb5ca4..67afa89311 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -25,7 +25,10 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // MARK: - Initialization private unowned let parent: RUMContextProvider - private let dependencies: RUMScopeDependencies + + /// Container bundling dependencies for this scope. + let dependencies: RUMScopeDependencies + /// If this is the very first view created in the current app process. private let isInitialView: Bool @@ -549,6 +552,9 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { // Update fatal error context with recent RUM view: dependencies.fatalErrorContext.view = event + + // Track this view in Session Ended metric: + sessionEndedMetric?.track(view: event) } else { // if event was dropped by mapper version -= 1 } diff --git a/DatadogRUM/Tests/RUMMonitor/RUMScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/RUMScopeTests.swift index f69403f622..e32ecb4f49 100644 --- a/DatadogRUM/Tests/RUMMonitor/RUMScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/RUMScopeTests.swift @@ -12,6 +12,7 @@ import DatadogInternal class RUMScopeTests: XCTestCase { /// A mock `RUMScope` that completes or not based on the configuration. private class CompletableScope: RUMScope { + let dependencies: RUMScopeDependencies = .mockAny() let isCompleted: Bool init(isCompleted: Bool) { From 37a8536bee4d807a8c466ed68760b11aa4498084 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 31 May 2024 12:48:13 +0200 Subject: [PATCH 45/71] RUM-1660 Add tests for "RUM Session Ended" metric spec --- ...UMSessionEndedMetricIntegrationTests.swift | 2 +- .../SDKMetrics/SessionEndedMetricTests.swift | 24 +++++++++++++++++++ .../Matchers/JSONObjectMatcher.swift | 19 +++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift index 7fba479ce5..2af8969a72 100644 --- a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift @@ -163,7 +163,7 @@ class RUMSessionEndedMetricIntegrationTests: XCTestCase { monitor.startView(key: "key2", name: "View2") dateProvider.now += 5.seconds monitor.stopView(key: "key2") - + // Simulate app in background: core.context = .mockWith(applicationStateHistory: .mockAppInBackground(since: dateProvider.now)) diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift index 1988097129..9060583b78 100644 --- a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift @@ -347,6 +347,30 @@ class SessionEndedMetricTests: XCTestCase { ["top_1_error": 9, "top__2_error": 8, "top_3_error": 7, "top4_error": 6] ) } + + // MARK: - Metric Spec + + func testEncodedMetricAttributesFollowTheSpec() throws { + // Given + let metric = SessionEndedMetric( + sessionID: sessionID, precondition: .mockRandom(), context: .mockWith(applicationBundleType: .iOSApp) + ) + metric.track(view: .mockRandomWith(sessionID: sessionID, viewTimeSpent: 10)) + + // When + let matcher = try JSONObjectMatcher(AnyEncodable(metric.asMetricAttributes())) + + // Then + XCTAssertNotNil(try matcher.value("rse.process_type") as String) + XCTAssertNotNil(try matcher.value("rse.precondition") as String) + XCTAssertNotNil(try matcher.value("rse.duration") as Int) + XCTAssertNotNil(try matcher.value("rse.was_stopped") as Bool) + XCTAssertNotNil(try matcher.value("rse.views_count.total") as Int) + XCTAssertNotNil(try matcher.value("rse.views_count.background") as Int) + XCTAssertNotNil(try matcher.value("rse.views_count.app_launch") as Int) + XCTAssertNotNil(try matcher.value("rse.sdk_errors_count.total") as Int) + XCTAssertNotNil(try matcher.value("rse.sdk_errors_count.by_kind") as [String: Int]) + } } private extension Int { diff --git a/TestUtilities/Matchers/JSONObjectMatcher.swift b/TestUtilities/Matchers/JSONObjectMatcher.swift index 2a23a69867..3bbb37c1cc 100644 --- a/TestUtilities/Matchers/JSONObjectMatcher.swift +++ b/TestUtilities/Matchers/JSONObjectMatcher.swift @@ -5,6 +5,7 @@ */ import Foundation +import DatadogInternal public enum JSONMatcherException: Error { case objectException(String) @@ -120,3 +121,21 @@ public class JSONArrrayMatcher { /// The number of elements in the array. public var count: Int { array.count } } + +// MARK: - Convenience + +public extension JSONObjectMatcher { + /// Instantiates `JSONObjectMatcher` with an encodable value. + /// + /// Note: Provided value is first encoded to JSON data and then gets decoded to `[String: Any]` + /// - Parameters: + /// - anyValue: the value to initialize matcher + /// - encoder: an instance of encoder to perform JSON serialization (defaults to SDK default encoder) + convenience init(_ anyValue: AnyEncodable, encoder: JSONEncoder = .dd.default()) throws { + let encoded = try encoder.encode(anyValue) + guard let jsonObject = try JSONSerialization.jsonObject(with: encoded) as? [String: Any] else { + throw JSONMatcherException.objectException("Encoded value can't be decoded [String: Any]") + } + self.init(object: jsonObject) + } +} From 180ab41116e9e338c9199f3129cfe27bb164d692 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 31 May 2024 15:42:56 +0200 Subject: [PATCH 46/71] RUM-1660 Fix lint --- DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift index aec4077bd4..7bb7dd32f0 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift @@ -56,7 +56,7 @@ internal final class SessionEndedMetric { /// Indicates if the session was stopped through `stopSession()` API. @ReadWriteLock - private var wasStopped: Bool = false + private var wasStopped = false // TODO: RUM-4591 Track diagnostic attributes: // - no_view_events_count From 5954c061b3a7cba12a7ca76d9e10350268cc9b22 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 31 May 2024 15:55:33 +0200 Subject: [PATCH 47/71] RUM-1660 Add more tests --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 ++++ .../Integrations/TelemetryInterecptor.swift | 2 ++ .../TelemetryInterceptorTests.swift | 31 +++++++++++++++++++ .../Integrations/TelemetryReceiverTests.swift | 21 +++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index efb8e74dc6..51df3ee8fd 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -533,6 +533,8 @@ 61C363802436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3637F2436164B00C4D4E6 /* ObjcExceptionHandlerTests.swift */; }; 61C3638324361BE200C4D4E6 /* DatadogPrivateMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3638224361BE200C4D4E6 /* DatadogPrivateMocks.swift */; }; 61C3638524361E9200C4D4E6 /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3638424361E9200C4D4E6 /* Globals.swift */; }; + 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */; }; + 61C4534B2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */; }; 61C5A89624509BF600DA608C /* TracerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C5A89524509BF600DA608C /* TracerTests.swift */; }; 61C713A32A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713A02A3B78F900FA735A /* RUMMonitorProtocol.swift */; }; 61C713A42A3B78F900FA735A /* RUMMonitorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C713A02A3B78F900FA735A /* RUMMonitorProtocol.swift */; }; @@ -2542,6 +2544,7 @@ 61C3E63824BF19B4008053F2 /* RUMContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMContext.swift; sourceTree = ""; }; 61C3E63A24BF1A4B008053F2 /* RUMCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMCommand.swift; sourceTree = ""; }; 61C3E63D24BF1B91008053F2 /* RUMApplicationScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMApplicationScope.swift; sourceTree = ""; }; + 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptorTests.swift; sourceTree = ""; }; 61C5A87824509A0C00DA608C /* DDSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDSpan.swift; sourceTree = ""; }; 61C5A87924509A0C00DA608C /* DDNoOps.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DDNoOps.swift; sourceTree = ""; }; 61C5A87C24509A0C00DA608C /* Casting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Casting.swift; sourceTree = ""; }; @@ -5846,6 +5849,7 @@ D21C26ED28AFB65B005DD405 /* ErrorMessageReceiverTests.swift */, 9E53889B2773C4B300A7DC42 /* WebViewEventReceiverTests.swift */, D248ED4728081B9B00B315B4 /* TelemetryReceiverTests.swift */, + 61C453492C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift */, ); path = Integrations; sourceTree = ""; @@ -8693,6 +8697,7 @@ 6188697D2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */, 61DCC8482C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */, D23F8EA029DDCD38001CFAE8 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, + 61C4534B2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */, D23F8EA229DDCD38001CFAE8 /* RUMSessionScopeTests.swift in Sources */, D23F8EA329DDCD38001CFAE8 /* RUMUserActionScopeTests.swift in Sources */, 615B0F8C2BB33C2800E9ED6C /* AppHangsMonitorTests.swift in Sources */, @@ -9012,6 +9017,7 @@ 6188697C2A4376F700E8996B /* RUMConfigurationTests.swift in Sources */, 61DCC8472C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */, D29A9FA629DDB483005C54A4 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, + 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */, D29A9FBD29DDB483005C54A4 /* RUMSessionScopeTests.swift in Sources */, D29A9FAB29DDB483005C54A4 /* RUMUserActionScopeTests.swift in Sources */, 615B0F8B2BB33C2800E9ED6C /* AppHangsMonitorTests.swift in Sources */, diff --git a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift b/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift index 1c1a66b0f7..f210740b35 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift @@ -7,7 +7,9 @@ import Foundation import DatadogInternal +/// Intercepts telemetry events sent through message bus. internal struct TelemetryInterecptor: FeatureMessageReceiver { + /// "RUM Session Ended" controller to count SDK errors. let sessionEndedMetric: SessionEndedMetricController func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { diff --git a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift new file mode 100644 index 0000000000..be72d5920d --- /dev/null +++ b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift @@ -0,0 +1,31 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +import TestUtilities +import DatadogInternal +@testable import DatadogRUM + +class TelemetryInterceptorTests: XCTestCase { + func testWhenInterceptingErrorTelemetry_itItUpdatesSessionEndedMetric() throws { + let sessionID = RUMUUID.mockRandom().toRUMDataFormat + + // Given + let metricController = SessionEndedMetricController(telemetry: NOPTelemetry()) + let interceptor = TelemetryInterecptor(sessionEndedMetric: metricController) + + // When + metricController.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockAny()) + let errorTelemetry: TelemetryMessage = .error(id: .mockAny(), message: .mockAny(), kind: .mockAny(), stack: .mockAny()) + let result = interceptor.receive(message: .telemetry(errorTelemetry), from: NOPDatadogCore()) + + // Then + XCTAssertFalse(result) + let metricAttributes = try XCTUnwrap(metricController.metric(for: sessionID)?.asMetricAttributes()) + let rse = try XCTUnwrap(metricAttributes[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes) + XCTAssertEqual(rse.sdkErrorsCount.total, 1) + } +} diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 8dfb2a28ed..b476ce3b0f 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -424,6 +424,27 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.action?.id, rumContext.userActionID) } + func testSendTelemetryMetricWithRUMContextAndSessionIDOverride() { + // Given + let rumContext: RUMCoreContext = .mockRandom() + featureScope.contextMock.baggages = [RUMFeature.name: FeatureBaggage(rumContext)] + let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) + let sessionIDOverride = "session-id-override" + + // When + var attributes = mockRandomAttributes() + attributes[SDKMetricFields.sessionIDOverrideKey] = sessionIDOverride + TelemetryMock(with: receiver).metric(name: .mockRandom(), attributes: attributes) + + // Then + let event = featureScope.eventsWritten(ofType: TelemetryDebugEvent.self).first + XCTAssertEqual(event?.application?.id, rumContext.applicationID) + XCTAssertEqual(event?.session?.id, sessionIDOverride) + XCTAssertEqual(event?.view?.id, rumContext.viewID) + XCTAssertEqual(event?.action?.id, rumContext.userActionID) + XCTAssertNil(event?.telemetry.telemetryInfo[SDKMetricFields.sessionIDOverrideKey], "It should delete `sessionIDOverrideKey` from metric attributes") + } + func testMethodCallTelemetryPropagetsAllData() throws { // Given let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) From 1e65da5e6463f7fa5403af3a2006599c6c147103 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 31 May 2024 16:05:09 +0200 Subject: [PATCH 48/71] RUM-1660 Fix lint --- DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift index be72d5920d..5262bc8615 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift @@ -12,7 +12,7 @@ import DatadogInternal class TelemetryInterceptorTests: XCTestCase { func testWhenInterceptingErrorTelemetry_itItUpdatesSessionEndedMetric() throws { let sessionID = RUMUUID.mockRandom().toRUMDataFormat - + // Given let metricController = SessionEndedMetricController(telemetry: NOPTelemetry()) let interceptor = TelemetryInterecptor(sessionEndedMetric: metricController) From 35c8096702ceb401da4493b5b1bf2b810d899272 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 10 Jun 2024 11:04:14 +0200 Subject: [PATCH 49/71] RUM-1660 CR feedback - reduce number of RW locks used to track session state --- .../Integrations/TelemetryInterecptor.swift | 2 +- DatadogRUM/Sources/RUMMonitor/RUMScope.swift | 7 - .../Scopes/RUMApplicationScope.swift | 2 +- .../RUMMonitor/Scopes/RUMSessionScope.swift | 4 +- .../RUMMonitor/Scopes/RUMViewScope.swift | 2 +- .../SDKMetrics/SessionEndedMetric.swift | 23 ++-- .../SessionEndedMetricController.swift | 49 ++++--- DatadogRUM/Sources/UUIDs/RUMUUID.swift | 2 +- .../TelemetryInterceptorTests.swift | 13 +- .../Tests/Mocks/RUMDataModelMocks.swift | 4 +- DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift | 2 +- .../SessionEndedMetricControllerTests.swift | 130 +++++++++--------- .../SDKMetrics/SessionEndedMetricTests.swift | 32 ++--- TestUtilities/Mocks/TelemetryMocks.swift | 5 + 14 files changed, 146 insertions(+), 131 deletions(-) diff --git a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift b/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift index f210740b35..af59a4e707 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift @@ -28,6 +28,6 @@ internal struct TelemetryInterecptor: FeatureMessageReceiver { } private func interceptError(id: String, message: String, kind: String, stack: String) { - sessionEndedMetric.latestMetric?.track(sdkErrorKind: kind) + sessionEndedMetric.track(sdkErrorKind: kind, in: nil) // `nil` - track in current session } } diff --git a/DatadogRUM/Sources/RUMMonitor/RUMScope.swift b/DatadogRUM/Sources/RUMMonitor/RUMScope.swift index f7f077bcc5..88b44e5b45 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMScope.swift @@ -26,13 +26,6 @@ extension RUMScope { } } -extension RUMScope where Self: RUMContextProvider { - /// The "RUM Session Ended" metric tracking the session that this `RUMScope` belongs to. - var sessionEndedMetric: SessionEndedMetric? { - dependencies.sessionEndedMetric.metric(for: context.sessionID.toRUMDataFormat) - } -} - extension Array where Element: RUMScope { /// Propagates given `command` through this array of scopes and manages their lifecycle by /// filtering scopes that get closed. diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index 3ac8ea74cf..305ef959d2 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -102,7 +102,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { // proccss(command:context:writer) returned false, so the scope will be deallocated at the end of // this execution context. End the "RUM Session Ended" metric: - defer { dependencies.sessionEndedMetric.endMetric(sessionID: scope.sessionUUID.toRUMDataFormat) } + defer { dependencies.sessionEndedMetric.endMetric(sessionID: scope.sessionUUID) } // proccss(command:context:writer) returned false, but if the scope is still active // it means the session reached one of the end reasons diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift index 0492f53868..cee8ae1a75 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMSessionScope.swift @@ -106,7 +106,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { // Start tracking "RUM Session Ended" metric for this session dependencies.sessionEndedMetric.startMetric( - sessionID: sessionUUID.toRUMDataFormat, + sessionID: sessionUUID, precondition: startPrecondition, context: context ) @@ -202,7 +202,7 @@ internal class RUMSessionScope: RUMScope, RUMContextProvider { var deactivating = false if isActive { if command is RUMStopSessionCommand { - sessionEndedMetric?.trackWasStopped() + dependencies.sessionEndedMetric.trackWasStopped(sessionID: self.context.sessionID) endReason = .stopAPI deactivating = true } else if let startApplicationCommand = command as? RUMApplicationStartCommand { diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 67afa89311..c8369b021d 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -554,7 +554,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { dependencies.fatalErrorContext.view = event // Track this view in Session Ended metric: - sessionEndedMetric?.track(view: event) + dependencies.sessionEndedMetric.track(view: event, in: self.context.sessionID) } else { // if event was dropped by mapper version -= 1 } diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift index 7bb7dd32f0..8dfa4ccd9a 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift @@ -7,8 +7,8 @@ import Foundation import DatadogInternal -/// An object tracking the state of RUM session and exporting attributes for "RUM Session Ended" telemetry. -internal final class SessionEndedMetric { +/// Tracks the state of RUM session and exports attributes for "RUM Session Ended" telemetry. +internal struct SessionEndedMetric { /// Definition of fields in "RUM Session Ended" telemetry, following the "RUM Session Ended" telemetry spec. internal enum Constants { /// The name of this metric, included in telemetry log. @@ -21,7 +21,7 @@ internal final class SessionEndedMetric { } /// An ID of the session being tracked through this metric object. - let sessionID: String + let sessionID: RUMUUID /// The type of OS component where the session was tracked. private let bundleType: BundleType @@ -39,23 +39,18 @@ internal final class SessionEndedMetric { } /// Stores information about tracked views, referencing them by their view ID. - @ReadWriteLock private var trackedViews: [String: TrackedViewInfo] = [:] /// Info about the first tracked view. - @ReadWriteLock private var firstTrackedView: TrackedViewInfo? /// Info about the last tracked view. - @ReadWriteLock private var lastTrackedView: TrackedViewInfo? /// Tracks the number of SDK errors by their kind. - @ReadWriteLock private var trackedSDKErrors: [String: Int] = [:] /// Indicates if the session was stopped through `stopSession()` API. - @ReadWriteLock private var wasStopped = false // TODO: RUM-4591 Track diagnostic attributes: @@ -72,7 +67,7 @@ internal final class SessionEndedMetric { /// - precondition: The precondition that led to starting this session. /// - context: The SDK context at the moment of starting this session. init( - sessionID: String, + sessionID: RUMUUID, precondition: RUMSessionPrecondition?, context: DatadogContext ) { @@ -82,8 +77,8 @@ internal final class SessionEndedMetric { } /// Tracks the view event that occurred during the session. - func track(view: RUMViewEvent) { - guard view.session.id == sessionID else { + mutating func track(view: RUMViewEvent) { + guard view.session.id == sessionID.toRUMDataFormat else { return // sanity check, unexpected } @@ -103,7 +98,7 @@ internal final class SessionEndedMetric { } /// Tracks the kind of SDK error that occurred during the session. - func track(sdkErrorKind: String) { + mutating func track(sdkErrorKind: String) { if let count = trackedSDKErrors[sdkErrorKind] { trackedSDKErrors[sdkErrorKind] = count + 1 } else { @@ -112,7 +107,7 @@ internal final class SessionEndedMetric { } /// Signals that the session was stopped with `stopSession()` API. - func trackWasStopped() { + mutating func trackWasStopped() { wasStopped = true } @@ -199,7 +194,7 @@ internal final class SessionEndedMetric { return [ SDKMetricFields.typeKey: Constants.typeValue, - SDKMetricFields.sessionIDOverrideKey: sessionID, + SDKMetricFields.sessionIDOverrideKey: sessionID.toRUMDataFormat, Constants.rseKey: Attributes( processType: { switch bundleType { diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift index 65bc7edde0..2103507b74 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift @@ -11,10 +11,10 @@ import DatadogInternal internal final class SessionEndedMetricController { /// Dictionary to keep track of pending metrics, keyed by session ID. @ReadWriteLock - private var metricsBySessionID: [String: SessionEndedMetric] = [:] - /// Array to keep track of pending metrics in their start order. + private var metricsBySessionID: [RUMUUID: SessionEndedMetric] = [:] + /// Array to keep track of pending session IDs in their start order. @ReadWriteLock - private var metrics: [SessionEndedMetric] = [] + private var pendingSessionIDs: [RUMUUID] = [] /// Telemetry endpoint for sending metrics. private let telemetry: Telemetry @@ -31,35 +31,52 @@ internal final class SessionEndedMetricController { /// - precondition: The precondition that led to starting this session. /// - context: The SDK context at the moment of starting this session. /// - Returns: The newly created `SessionEndedMetric` instance. - func startMetric(sessionID: String, precondition: RUMSessionPrecondition?, context: DatadogContext) { - guard sessionID != RUMUUID.nullUUID.toRUMDataFormat else { + func startMetric(sessionID: RUMUUID, precondition: RUMSessionPrecondition?, context: DatadogContext) { + guard sessionID != RUMUUID.nullUUID else { return // do not track metric when session is not sampled } let metric = SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context) metricsBySessionID[sessionID] = metric - metrics.append(metric) + pendingSessionIDs.append(sessionID) } - /// Retrieves the metric for a given session ID. - /// - Parameter sessionID: The ID of the session to retrieve the metric for. - /// - Returns: The `SessionEndedMetric` instance if found, otherwise `nil`. - func metric(for sessionID: String) -> SessionEndedMetric? { - return metricsBySessionID[sessionID] + /// Tracks the view event that occurred during the session. + /// - Parameters: + /// - view: the view event to track + /// - sessionID: session ID to track this view in (pass `nil` to track it for the last started session) + func track(view: RUMViewEvent, in sessionID: RUMUUID?) { + updateMetric(for: sessionID) { $0?.track(view: view) } + } + + /// Tracks the kind of SDK error that occurred during the session. + /// - Parameters: + /// - sdkErrorKind: the kind of SDK error to track + /// - sessionID: session ID to track this error in (pass `nil` to track it for the last started session) + func track(sdkErrorKind: String, in sessionID: RUMUUID?) { + updateMetric(for: sessionID) { $0?.track(sdkErrorKind: sdkErrorKind) } } - /// Retrieves the last started metric. - var latestMetric: SessionEndedMetric? { - return metrics.last + /// Signals that the session was stopped with `stopSession()` API. + /// - Parameter sessionID: session ID to mark as stopped (pass `nil` to track it for the last started session) + func trackWasStopped(sessionID: RUMUUID?) { + updateMetric(for: sessionID) { $0?.trackWasStopped() } } /// Ends the metric for a given session, sending it to telemetry and removing it from pending metrics. /// - Parameter sessionID: The ID of the session to end the metric for. - func endMetric(sessionID: String) { + func endMetric(sessionID: RUMUUID) { guard let metric = metricsBySessionID[sessionID] else { return } telemetry.metric(name: SessionEndedMetric.Constants.name, attributes: metric.asMetricAttributes()) metricsBySessionID[sessionID] = nil - metrics.removeAll(where: { $0 === metric }) // O(n), but "ending the metric" is very rare event + pendingSessionIDs.removeAll(where: { $0 == sessionID }) // O(n), but "ending the metric" is very rare event + } + + private func updateMetric(for sessionID: RUMUUID?, _ mutation: (inout SessionEndedMetric?) -> Void) { + guard let sessionID = (sessionID ?? pendingSessionIDs.last) else { + return + } + _metricsBySessionID.mutate { metrics in mutation(&metrics[sessionID]) } } } diff --git a/DatadogRUM/Sources/UUIDs/RUMUUID.swift b/DatadogRUM/Sources/UUIDs/RUMUUID.swift index fe8a6eafa7..f37873a51c 100644 --- a/DatadogRUM/Sources/UUIDs/RUMUUID.swift +++ b/DatadogRUM/Sources/UUIDs/RUMUUID.swift @@ -6,7 +6,7 @@ import Foundation -internal struct RUMUUID: Equatable { +internal struct RUMUUID: Equatable, Hashable { let rawValue: UUID /// UUID with all zeros, used to represent no-op values. diff --git a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift index 5262bc8615..1a439bb8df 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift @@ -10,22 +10,25 @@ import DatadogInternal @testable import DatadogRUM class TelemetryInterceptorTests: XCTestCase { + private let telemetry = TelemetryMock() + func testWhenInterceptingErrorTelemetry_itItUpdatesSessionEndedMetric() throws { - let sessionID = RUMUUID.mockRandom().toRUMDataFormat + let sessionID: RUMUUID = .mockRandom() // Given - let metricController = SessionEndedMetricController(telemetry: NOPTelemetry()) + let metricController = SessionEndedMetricController(telemetry: telemetry) let interceptor = TelemetryInterecptor(sessionEndedMetric: metricController) // When metricController.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockAny()) let errorTelemetry: TelemetryMessage = .error(id: .mockAny(), message: .mockAny(), kind: .mockAny(), stack: .mockAny()) let result = interceptor.receive(message: .telemetry(errorTelemetry), from: NOPDatadogCore()) + XCTAssertFalse(result) // Then - XCTAssertFalse(result) - let metricAttributes = try XCTUnwrap(metricController.metric(for: sessionID)?.asMetricAttributes()) - let rse = try XCTUnwrap(metricAttributes[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes) + metricController.endMetric(sessionID: sessionID) + let metric = try XCTUnwrap(telemetry.messages.lastMetric(named: SessionEndedMetric.Constants.name)) + let rse = try XCTUnwrap(metric.attributes[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes) XCTAssertEqual(rse.sdkErrorsCount.total, 1) } } diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index e21f7405ec..27b116422e 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -131,7 +131,7 @@ extension RUMViewEvent: RandomMockable { /// Produces random `RUMViewEvent` with setting given fields to certain values. static func mockRandomWith( - sessionID: String = .mockRandom(), + sessionID: RUMUUID = .mockRandom(), viewID: String = .mockRandom(), date: Int64 = .mockRandom(), viewIsActive: Bool? = .random(), @@ -166,7 +166,7 @@ extension RUMViewEvent: RandomMockable { service: .mockRandom(), session: .init( hasReplay: nil, - id: sessionID, + id: sessionID.toRUMDataFormat, isActive: true, sampledForReplay: nil, type: .user diff --git a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift index f96ca1021d..9145d1b56b 100644 --- a/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift @@ -689,7 +689,7 @@ extension RUMInternalErrorSource: RandomMockable { // MARK: - RUMContext Mocks -extension RUMUUID { +extension RUMUUID: RandomMockable { public static func mockRandom() -> RUMUUID { return RUMUUID(rawValue: UUID()) } diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift index 57c7bb1952..8b44bdc2b7 100644 --- a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift @@ -12,86 +12,86 @@ import DatadogInternal class SessionEndedMetricControllerTests: XCTestCase { private let telemetry = TelemetryMock() - func testWhenMetricIsStarted_itCanBeRetrievedByID() throws { + func testTrackingSingleSessionWithExplicitSessionID() throws { + let sessionID: RUMUUID = .mockRandom() + let viewIDs: [String] = .mockRandom(count: 5) + let errorKinds: [String] = .mockRandom(count: 5) + + // Given let controller = SessionEndedMetricController(telemetry: telemetry) + controller.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - let sessionID1: String = .mockRandom() - let sessionID2: String = .mockRandom() - controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom()) - controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom()) + viewIDs.forEach { controller.track(view: .mockRandomWith(sessionID: sessionID, viewID: $0), in: sessionID) } + errorKinds.forEach { controller.track(sdkErrorKind: $0, in: sessionID) } + controller.trackWasStopped(sessionID: sessionID) + controller.endMetric(sessionID: sessionID) // Then - let metric1 = try XCTUnwrap(controller.metric(for: sessionID1)) - let metric2 = try XCTUnwrap(controller.metric(for: sessionID2)) - XCTAssertEqual(metric1.sessionID, sessionID1) - XCTAssertEqual(metric2.sessionID, sessionID2) + let metric = try XCTUnwrap(telemetry.messages.lastSessionEndedMetric) + XCTAssertEqual(metric.viewsCount.total, viewIDs.count) + XCTAssertEqual(metric.sdkErrorsCount.total, errorKinds.count) + XCTAssertEqual(metric.wasStopped, true) } - func testWhenMetricIsStarted_itCanBeRetrievedAsLatest() throws { - let controller = SessionEndedMetricController(telemetry: telemetry) + func testTrackingMultipleSessionsWithExplicitSessionID() throws { + let sessionID1: RUMUUID = .mockRandom() + let sessionID2: RUMUUID = .mockRandom() // When - let sessionID1: String = .mockRandom() - let sessionID2: String = .mockRandom() + let controller = SessionEndedMetricController(telemetry: telemetry) controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom()) controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom()) - - // Then - XCTAssertEqual(controller.latestMetric?.sessionID, sessionID2) - controller.endMetric(sessionID: sessionID2) - XCTAssertEqual(controller.latestMetric?.sessionID, sessionID1) + // Session 1: + controller.track(view: .mockRandomWith(sessionID: sessionID1), in: sessionID1) + controller.track(sdkErrorKind: "error.kind1", in: sessionID1) + controller.trackWasStopped(sessionID: sessionID1) + // Session 2: + controller.track(sdkErrorKind: "error.kind2", in: sessionID2) + // Send 1st and 2nd: controller.endMetric(sessionID: sessionID1) - XCTAssertNil(controller.latestMetric) - } - - func testWhenMetricIsEnded_itIsSentToTelemetry() throws { - let sessionID: String = .mockRandom() - let controller = SessionEndedMetricController(telemetry: telemetry) - controller.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) - - // When - controller.endMetric(sessionID: sessionID) + let metric1 = try XCTUnwrap(telemetry.messages.lastSessionEndedMetric) + controller.endMetric(sessionID: sessionID2) + let metric2 = try XCTUnwrap(telemetry.messages.lastSessionEndedMetric) // Then - let metric = try XCTUnwrap(telemetry.messages.firstMetric(named: SessionEndedMetric.Constants.name)) - XCTAssertEqual(metric.attributes[SDKMetricFields.typeKey] as? String, SessionEndedMetric.Constants.typeValue) - XCTAssertEqual(metric.attributes[SDKMetricFields.sessionIDOverrideKey] as? String, sessionID) + XCTAssertEqual(metric1.viewsCount.total, 1) + XCTAssertEqual(metric1.sdkErrorsCount.total, 1) + XCTAssertEqual(metric1.sdkErrorsCount.byKind["error_kind1"], 1) + XCTAssertEqual(metric1.wasStopped, true) + + XCTAssertEqual(metric2.viewsCount.total, 0) + XCTAssertEqual(metric2.sdkErrorsCount.total, 1) + XCTAssertEqual(metric2.sdkErrorsCount.byKind["error_kind2"], 1) + XCTAssertEqual(metric2.wasStopped, false) } - func testAfterMetricIsEnded_itCanNoLongerBeRetrieved() throws { - let sessionID: String = .mockRandom() - let controller = SessionEndedMetricController(telemetry: telemetry) - controller.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + func testTrackingLatestSession() throws { + let sessionID1: RUMUUID = .mockRandom() + let sessionID2: RUMUUID = .mockRandom() // When - XCTAssertNotNil(controller.metric(for: sessionID)) - controller.endMetric(sessionID: sessionID) - - // Then - XCTAssertNil(controller.metric(for: sessionID)) - XCTAssertNil(controller.latestMetric) - } - - func testWhenSessionIsSampled_itDoesNotTrackMetric() throws { let controller = SessionEndedMetricController(telemetry: telemetry) - - // When - let rejectedSessionID = RUMUUID.nullUUID.toRUMDataFormat - controller.startMetric(sessionID: rejectedSessionID, precondition: .mockRandom(), context: .mockRandom()) + controller.startMetric(sessionID: sessionID1, precondition: .mockRandom(), context: .mockRandom()) + controller.startMetric(sessionID: sessionID2, precondition: .mockRandom(), context: .mockRandom()) + // Track latest session (`sessionID: nil`) + controller.track(view: .mockRandomWith(sessionID: sessionID2), in: nil) + controller.track(sdkErrorKind: "error.kind1", in: nil) + controller.trackWasStopped(sessionID: nil) + // Send 2nd: + controller.endMetric(sessionID: sessionID2) + let metric = try XCTUnwrap(telemetry.messages.lastSessionEndedMetric) // Then - XCTAssertNil(controller.metric(for: rejectedSessionID)) - XCTAssertNil(controller.latestMetric) - - controller.endMetric(sessionID: rejectedSessionID) - XCTAssertTrue(telemetry.messages.isEmpty) + XCTAssertEqual(metric.viewsCount.total, 1) + XCTAssertEqual(metric.sdkErrorsCount.total, 1) + XCTAssertEqual(metric.wasStopped, true) } // MARK: - Thread Safety func testTrackingSessionEndedMetricIsThreadSafe() { - let sessionIDs: [String] = .mockRandom(count: 10) + let sessionIDs: [RUMUUID] = .mockRandom(count: 10) let controller = SessionEndedMetricController(telemetry: telemetry) // swiftlint:disable opening_brace @@ -100,16 +100,9 @@ class SessionEndedMetricControllerTests: XCTestCase { { controller.startMetric( sessionID: sessionIDs.randomElement()!, precondition: .mockRandom(), context: .mockRandom() ) }, - { _ = controller.metric(for: sessionIDs.randomElement()!) }, - { - _ = controller.metric(for: sessionIDs.randomElement()!)?.track(view: .mockRandom()) - }, - { - _ = controller.metric(for: sessionIDs.randomElement()!)?.track(sdkErrorKind: .mockRandom()) - }, - { - _ = controller.metric(for: sessionIDs.randomElement()!)?.trackWasStopped() - }, + { controller.track(view: .mockRandom(), in: sessionIDs.randomElement()!) }, + { controller.track(sdkErrorKind: .mockRandom(), in: sessionIDs.randomElement()!) }, + { controller.trackWasStopped(sessionID: sessionIDs.randomElement()!) }, { controller.endMetric(sessionID: sessionIDs.randomElement()!) }, ], iterations: 100 @@ -117,3 +110,12 @@ class SessionEndedMetricControllerTests: XCTestCase { // swiftlint:enable opening_brace } } + +// MARK: - Helpers + +private extension Array where Element == TelemetryMessage { + var lastSessionEndedMetric: SessionEndedMetric.Attributes? { + return lastMetric(named: SessionEndedMetric.Constants.name)? + .attributes[SessionEndedMetric.Constants.rseKey] as? SessionEndedMetric.Attributes + } +} diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift index 9060583b78..384eeeb005 100644 --- a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift @@ -12,7 +12,7 @@ import DatadogInternal class SessionEndedMetricTests: XCTestCase { private typealias Constants = SessionEndedMetric.Constants private typealias SessionEndedAttributes = SessionEndedMetric.Attributes - private let sessionID: String = .mockRandom() + private let sessionID: RUMUUID = .mockRandom() func testReportingEmptyMetric() throws { // Given @@ -54,7 +54,7 @@ class SessionEndedMetricTests: XCTestCase { let attributes = metric.asMetricAttributes() // Then - XCTAssertEqual(attributes[SDKMetricFields.sessionIDOverrideKey] as? String, sessionID) + XCTAssertEqual(attributes[SDKMetricFields.sessionIDOverrideKey] as? String, sessionID.toRUMDataFormat) } // MARK: - Process Type @@ -108,7 +108,7 @@ class SessionEndedMetricTests: XCTestCase { func testComputingDurationFromSingleView() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) let view: RUMViewEvent = .mockRandomWith(sessionID: sessionID) // When @@ -122,7 +122,7 @@ class SessionEndedMetricTests: XCTestCase { func testComputingDurationFromMultipleViews() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) let view1: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms, viewTimeSpent: 10.s2ns) let view2: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms + 10.s2ms, viewTimeSpent: 20.s2ns) let view3: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms + 10.s2ms + 20.s2ms, viewTimeSpent: 50.s2ns) @@ -140,7 +140,7 @@ class SessionEndedMetricTests: XCTestCase { func testComputingDurationFromOverlappingViews() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) let view1: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms, viewTimeSpent: 10.s2ns) let view2: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 15.s2ms, viewTimeSpent: 20.s2ns) // starts in the middle of `view1` @@ -156,7 +156,7 @@ class SessionEndedMetricTests: XCTestCase { func testDurationIsAlwaysComputedFromTheFirstAndLastView() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) let firstView: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 5.s2ms, viewTimeSpent: 10.s2ns) let lastView: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 5.s2ms + 10.s2ms, viewTimeSpent: 20.s2ns) @@ -173,7 +173,7 @@ class SessionEndedMetricTests: XCTestCase { func testWhenComputingDuration_itIgnoresViewsFromDifferentSession() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When metric.track(view: .mockRandom()) @@ -189,7 +189,7 @@ class SessionEndedMetricTests: XCTestCase { func testReportingSessionThatWasStopped() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When metric.trackWasStopped() @@ -218,7 +218,7 @@ class SessionEndedMetricTests: XCTestCase { let viewIDs: Set = .mockRandom(count: .mockRandom(min: 1, max: 10)) // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When viewIDs.forEach { metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: $0)) } @@ -235,7 +235,7 @@ class SessionEndedMetricTests: XCTestCase { let viewIDs = backgroundViewIDs.union(otherViewIDs) // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When viewIDs.forEach { viewID in @@ -255,7 +255,7 @@ class SessionEndedMetricTests: XCTestCase { let viewIDs = appLaunchViewIDs.union(otherViewIDs) // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When viewIDs.forEach { viewID in @@ -271,7 +271,7 @@ class SessionEndedMetricTests: XCTestCase { func testReportingViewsCount_itIgnoresViewsFromDifferentSession() throws { // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When metric.track(view: .mockRandom()) @@ -290,7 +290,7 @@ class SessionEndedMetricTests: XCTestCase { let repetitions = 5 // Given - let metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) + var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When (0.. (name: String, attributes: [String: Encodable])? { + return compactMap({ $0.asMetric }).filter({ $0.name == metricName }).last + } + /// Returns attributes of the first debug telemetry in this array. func firstDebug() -> (id: String, message: String, attributes: [String: Encodable]?)? { return compactMap { $0.asDebug }.first From 85eb3236e88de810ed5e23ad611462ed3f65baba Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 10 Jun 2024 11:05:45 +0200 Subject: [PATCH 50/71] RUM-1660 CR feedback - simplify tests setup --- .../RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift index 2af8969a72..307ec2fe97 100644 --- a/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/SDKMetrics/RUMSessionEndedMetricIntegrationTests.swift @@ -14,7 +14,6 @@ class RUMSessionEndedMetricIntegrationTests: XCTestCase { private var rumConfig: RUM.Configuration! // swiftlint:disable:this implicitly_unwrapped_optional override func setUp() { - super.setUp() core = DatadogCoreProxy() core.context = .mockWith( launchTime: .mockWith(launchDate: dateProvider.now), @@ -30,7 +29,6 @@ class RUMSessionEndedMetricIntegrationTests: XCTestCase { core.flushAndTearDown() core = nil rumConfig = nil - super.tearDown() } // MARK: - Conditions For Sending The Metric From 297b1ac5df8290be5b2ddea7988cd3a2445a77b9 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 10 Jun 2024 11:34:42 +0200 Subject: [PATCH 51/71] RUM-1660 CR feedback - send telemetry on tracking view in foreign session --- .../SDKMetrics/SessionEndedMetric.swift | 19 +++++++++- .../SessionEndedMetricController.swift | 12 ++++-- .../SDKMetrics/SessionEndedMetricTests.swift | 38 +++++++++---------- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift index 8dfa4ccd9a..3d417d7c9e 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetric.swift @@ -7,6 +7,21 @@ import Foundation import DatadogInternal +internal enum SessionEndedMetricError: Error, CustomStringConvertible { + /// Indicates an attempt of tracking view event in session that shouldn't belong to. + case trackingViewInForeignSession(viewURL: String, sessionID: RUMUUID) + + var description: String { + switch self { + case .trackingViewInForeignSession(let viewURL, let sessionID): + let isAppLaunchView = viewURL == RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL + let isBackgroundView = viewURL == RUMOffViewEventsHandlingRule.Constants.backgroundViewURL + let viewKind = isAppLaunchView ? "AppLaunch" : (isBackgroundView ? "Background" : "Custom") + return "Attempted to track \(viewKind) view in session with different UUID \(sessionID)" + } + } +} + /// Tracks the state of RUM session and exports attributes for "RUM Session Ended" telemetry. internal struct SessionEndedMetric { /// Definition of fields in "RUM Session Ended" telemetry, following the "RUM Session Ended" telemetry spec. @@ -77,9 +92,9 @@ internal struct SessionEndedMetric { } /// Tracks the view event that occurred during the session. - mutating func track(view: RUMViewEvent) { + mutating func track(view: RUMViewEvent) throws { guard view.session.id == sessionID.toRUMDataFormat else { - return // sanity check, unexpected + throw SessionEndedMetricError.trackingViewInForeignSession(viewURL: view.view.url, sessionID: sessionID) } var info = trackedViews[view.view.id] ?? TrackedViewInfo( diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift index 2103507b74..9bde5d17cb 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift @@ -45,7 +45,7 @@ internal final class SessionEndedMetricController { /// - view: the view event to track /// - sessionID: session ID to track this view in (pass `nil` to track it for the last started session) func track(view: RUMViewEvent, in sessionID: RUMUUID?) { - updateMetric(for: sessionID) { $0?.track(view: view) } + updateMetric(for: sessionID) { try $0?.track(view: view) } } /// Tracks the kind of SDK error that occurred during the session. @@ -73,10 +73,16 @@ internal final class SessionEndedMetricController { pendingSessionIDs.removeAll(where: { $0 == sessionID }) // O(n), but "ending the metric" is very rare event } - private func updateMetric(for sessionID: RUMUUID?, _ mutation: (inout SessionEndedMetric?) -> Void) { + private func updateMetric(for sessionID: RUMUUID?, _ mutation: (inout SessionEndedMetric?) throws -> Void) { guard let sessionID = (sessionID ?? pendingSessionIDs.last) else { return } - _metricsBySessionID.mutate { metrics in mutation(&metrics[sessionID]) } + _metricsBySessionID.mutate { metrics in + do { + try mutation(&metrics[sessionID]) + } catch let error { + telemetry.error(error) + } + } } } diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift index 384eeeb005..a0d0e5846a 100644 --- a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricTests.swift @@ -112,7 +112,7 @@ class SessionEndedMetricTests: XCTestCase { let view: RUMViewEvent = .mockRandomWith(sessionID: sessionID) // When - metric.track(view: view) + try metric.track(view: view) let attributes = metric.asMetricAttributes() // Then @@ -128,9 +128,9 @@ class SessionEndedMetricTests: XCTestCase { let view3: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 10.s2ms + 10.s2ms + 20.s2ms, viewTimeSpent: 50.s2ns) // When - metric.track(view: view1) - metric.track(view: view2) - metric.track(view: view3) + try metric.track(view: view1) + try metric.track(view: view2) + try metric.track(view: view3) let attributes = metric.asMetricAttributes() // Then @@ -145,8 +145,8 @@ class SessionEndedMetricTests: XCTestCase { let view2: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 15.s2ms, viewTimeSpent: 20.s2ns) // starts in the middle of `view1` // When - metric.track(view: view1) - metric.track(view: view2) + try metric.track(view: view1) + try metric.track(view: view2) let attributes = metric.asMetricAttributes() // Then @@ -161,9 +161,9 @@ class SessionEndedMetricTests: XCTestCase { let lastView: RUMViewEvent = .mockRandomWith(sessionID: sessionID, date: 5.s2ms + 10.s2ms, viewTimeSpent: 20.s2ns) // When - metric.track(view: firstView) - (0..<10).forEach { _ in metric.track(view: .mockRandom()) } // middle views should not alter the duration - metric.track(view: lastView) + try metric.track(view: firstView) + try (0..<10).forEach { _ in try metric.track(view: .mockRandomWith(sessionID: sessionID)) } // middle views should not alter the duration + try metric.track(view: lastView) let attributes = metric.asMetricAttributes() // Then @@ -176,8 +176,8 @@ class SessionEndedMetricTests: XCTestCase { var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - metric.track(view: .mockRandom()) - metric.track(view: .mockRandom()) + XCTAssertThrowsError(try metric.track(view: .mockRandom())) + XCTAssertThrowsError(try metric.track(view: .mockRandom())) let attributes = metric.asMetricAttributes() // Then @@ -221,7 +221,7 @@ class SessionEndedMetricTests: XCTestCase { var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - viewIDs.forEach { metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: $0)) } + try viewIDs.forEach { try metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: $0)) } let attributes = metric.asMetricAttributes() // Then @@ -238,9 +238,9 @@ class SessionEndedMetricTests: XCTestCase { var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - viewIDs.forEach { viewID in + try viewIDs.forEach { viewID in let viewURL = backgroundViewIDs.contains(viewID) ? RUMOffViewEventsHandlingRule.Constants.backgroundViewURL : .mockRandom() - metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: viewID, viewURL: viewURL)) + try metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: viewID, viewURL: viewURL)) } let attributes = metric.asMetricAttributes() @@ -258,9 +258,9 @@ class SessionEndedMetricTests: XCTestCase { var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - viewIDs.forEach { viewID in + try viewIDs.forEach { viewID in let viewURL = appLaunchViewIDs.contains(viewID) ? RUMOffViewEventsHandlingRule.Constants.applicationLaunchViewURL : .mockRandom() - metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: viewID, viewURL: viewURL)) + try metric.track(view: .mockRandomWith(sessionID: sessionID, viewID: viewID, viewURL: viewURL)) } let attributes = metric.asMetricAttributes() @@ -274,8 +274,8 @@ class SessionEndedMetricTests: XCTestCase { var metric = SessionEndedMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockRandom()) // When - metric.track(view: .mockRandom()) - metric.track(view: .mockRandom()) + XCTAssertThrowsError(try metric.track(view: .mockRandom())) + XCTAssertThrowsError(try metric.track(view: .mockRandom())) let attributes = metric.asMetricAttributes() // Then @@ -355,7 +355,7 @@ class SessionEndedMetricTests: XCTestCase { var metric = SessionEndedMetric( sessionID: sessionID, precondition: .mockRandom(), context: .mockWith(applicationBundleType: .iOSApp) ) - metric.track(view: .mockRandomWith(sessionID: sessionID, viewTimeSpent: 10)) + try metric.track(view: .mockRandomWith(sessionID: sessionID, viewTimeSpent: 10)) // When let matcher = try JSONObjectMatcher(AnyEncodable(metric.asMetricAttributes())) From 68aa7b75d9bd0417e1da3371b8cb04e923b3ded2 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 14:23:42 +0200 Subject: [PATCH 52/71] RUM-1660 CR feedback - fix typo --- Datadog/Datadog.xcodeproj/project.pbxproj | 12 ++++++------ DatadogRUM/Sources/Feature/RUMFeature.swift | 2 +- ...yInterecptor.swift => TelemetryInterceptor.swift} | 2 +- .../Integrations/TelemetryInterceptorTests.swift | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename DatadogRUM/Sources/Integrations/{TelemetryInterecptor.swift => TelemetryInterceptor.swift} (94%) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 51df3ee8fd..428b89938b 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -598,8 +598,8 @@ 61DCC8482C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */; }; 61DCC84A2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */; }; 61DCC84B2C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */; }; - 61DCC84E2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */; }; - 61DCC84F2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */; }; + 61DCC84E2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */; }; + 61DCC84F2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */; }; 61E45BE724519A3700F2C652 /* JSONDataMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45BE624519A3700F2C652 /* JSONDataMatcher.swift */; }; 61E45ED12451A8730061DAC7 /* SpanMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E45ED02451A8730061DAC7 /* SpanMatcher.swift */; }; 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E5333724B84EE2003D6C4E /* DebugRUMViewController.swift */; }; @@ -2597,7 +2597,7 @@ 61DB33B125DEDFC200F7EA71 /* CustomObjcViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomObjcViewController.m; sourceTree = ""; }; 61DCC8462C05CD0000CB59E5 /* SessionEndedMetricControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEndedMetricControllerTests.swift; sourceTree = ""; }; 61DCC8492C05D4D600CB59E5 /* RUMSessionEndedMetricIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMSessionEndedMetricIntegrationTests.swift; sourceTree = ""; }; - 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterecptor.swift; sourceTree = ""; }; + 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryInterceptor.swift; sourceTree = ""; }; 61DE333525C8278A008E3EC2 /* CrashReportingPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReportingPlugin.swift; sourceTree = ""; }; 61E45BCE2450A6EC00F2C652 /* TraceIDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceIDTests.swift; sourceTree = ""; }; 61E45BD12450F65B00F2C652 /* SpanEventBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanEventBuilderTests.swift; sourceTree = ""; }; @@ -4644,7 +4644,7 @@ D236BE2729520FED00676E67 /* CrashReportReceiver.swift */, D215ED6A29D2E1080046B721 /* ErrorMessageReceiver.swift */, D214DAA729E54CB4004D0AE8 /* TelemetryReceiver.swift */, - 61DCC84D2C071DCD00CB59E5 /* TelemetryInterecptor.swift */, + 61DCC84D2C071DCD00CB59E5 /* TelemetryInterceptor.swift */, ); path = Integrations; sourceTree = ""; @@ -8686,7 +8686,7 @@ D23F8E8D29DDCD28001CFAE8 /* VitalRefreshRateReader.swift in Sources */, D23F8E8E29DDCD28001CFAE8 /* UIKitRUMUserActionsHandler.swift in Sources */, D23F8E8F29DDCD28001CFAE8 /* RUMUUIDGenerator.swift in Sources */, - 61DCC84F2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */, + 61DCC84F2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9006,7 +9006,7 @@ D29A9F8929DD85BB005C54A4 /* VitalRefreshRateReader.swift in Sources */, D29A9F6929DD85BB005C54A4 /* UIKitRUMUserActionsHandler.swift in Sources */, D29A9F5229DD85BB005C54A4 /* RUMUUIDGenerator.swift in Sources */, - 61DCC84E2C071DCD00CB59E5 /* TelemetryInterecptor.swift in Sources */, + 61DCC84E2C071DCD00CB59E5 /* TelemetryInterceptor.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index cd6903893f..ead9764c04 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -100,7 +100,7 @@ internal final class RUMFeature: DatadogRemoteFeature { telemetry: core.telemetry ) self.messageReceiver = CombinedFeatureMessageReceiver( - TelemetryInterecptor(sessionEndedMetric: sessionEndedMetric), + TelemetryInterceptor(sessionEndedMetric: sessionEndedMetric), TelemetryReceiver( featureScope: featureScope, dateProvider: configuration.dateProvider, diff --git a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift b/DatadogRUM/Sources/Integrations/TelemetryInterceptor.swift similarity index 94% rename from DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift rename to DatadogRUM/Sources/Integrations/TelemetryInterceptor.swift index af59a4e707..ec4b7b9185 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryInterecptor.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryInterceptor.swift @@ -8,7 +8,7 @@ import Foundation import DatadogInternal /// Intercepts telemetry events sent through message bus. -internal struct TelemetryInterecptor: FeatureMessageReceiver { +internal struct TelemetryInterceptor: FeatureMessageReceiver { /// "RUM Session Ended" controller to count SDK errors. let sessionEndedMetric: SessionEndedMetricController diff --git a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift index 1a439bb8df..0fe552c2c1 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryInterceptorTests.swift @@ -17,7 +17,7 @@ class TelemetryInterceptorTests: XCTestCase { // Given let metricController = SessionEndedMetricController(telemetry: telemetry) - let interceptor = TelemetryInterecptor(sessionEndedMetric: metricController) + let interceptor = TelemetryInterceptor(sessionEndedMetric: metricController) // When metricController.startMetric(sessionID: sessionID, precondition: .mockRandom(), context: .mockAny()) From 7fae496f6dc2add4b75ca2d547bba48d921e5f2f Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 11 Jun 2024 14:25:02 +0200 Subject: [PATCH 53/71] RUM-1660 CR feedback - use single lock for tracking session state and recent IDs --- .../SessionEndedMetricController.swift | 20 ++++++++++--------- .../SessionEndedMetricControllerTests.swift | 3 +++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift index 9bde5d17cb..6a1ca81354 100644 --- a/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift +++ b/DatadogRUM/Sources/SDKMetrics/SessionEndedMetricController.swift @@ -13,7 +13,6 @@ internal final class SessionEndedMetricController { @ReadWriteLock private var metricsBySessionID: [RUMUUID: SessionEndedMetric] = [:] /// Array to keep track of pending session IDs in their start order. - @ReadWriteLock private var pendingSessionIDs: [RUMUUID] = [] /// Telemetry endpoint for sending metrics. @@ -35,9 +34,10 @@ internal final class SessionEndedMetricController { guard sessionID != RUMUUID.nullUUID else { return // do not track metric when session is not sampled } - let metric = SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context) - metricsBySessionID[sessionID] = metric - pendingSessionIDs.append(sessionID) + _metricsBySessionID.mutate { metrics in + metrics[sessionID] = SessionEndedMetric(sessionID: sessionID, precondition: precondition, context: context) + pendingSessionIDs.append(sessionID) + } } /// Tracks the view event that occurred during the session. @@ -69,15 +69,17 @@ internal final class SessionEndedMetricController { return } telemetry.metric(name: SessionEndedMetric.Constants.name, attributes: metric.asMetricAttributes()) - metricsBySessionID[sessionID] = nil - pendingSessionIDs.removeAll(where: { $0 == sessionID }) // O(n), but "ending the metric" is very rare event + _metricsBySessionID.mutate { metrics in + metrics[sessionID] = nil + pendingSessionIDs.removeAll(where: { $0 == sessionID }) // O(n), but "ending the metric" is very rare event + } } private func updateMetric(for sessionID: RUMUUID?, _ mutation: (inout SessionEndedMetric?) throws -> Void) { - guard let sessionID = (sessionID ?? pendingSessionIDs.last) else { - return - } _metricsBySessionID.mutate { metrics in + guard let sessionID = (sessionID ?? pendingSessionIDs.last) else { + return + } do { try mutation(&metrics[sessionID]) } catch let error { diff --git a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift index 8b44bdc2b7..9452988c7f 100644 --- a/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift +++ b/DatadogRUM/Tests/SDKMetrics/SessionEndedMetricControllerTests.swift @@ -103,6 +103,9 @@ class SessionEndedMetricControllerTests: XCTestCase { { controller.track(view: .mockRandom(), in: sessionIDs.randomElement()!) }, { controller.track(sdkErrorKind: .mockRandom(), in: sessionIDs.randomElement()!) }, { controller.trackWasStopped(sessionID: sessionIDs.randomElement()!) }, + { controller.track(view: .mockRandom(), in: nil) }, + { controller.track(sdkErrorKind: .mockRandom(), in: nil) }, + { controller.trackWasStopped(sessionID: nil) }, { controller.endMetric(sessionID: sessionIDs.randomElement()!) }, ], iterations: 100 From f74929ad7220715207ee2f4d34f9d890612fc9ad Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Fri, 17 May 2024 12:29:25 +0100 Subject: [PATCH 54/71] RUM-4133 Propagate device and os info to all metrics --- .../Integrations/TelemetryReceiver.swift | 34 ++++++++----------- .../Integrations/TelemetryReceiverTests.swift | 10 +++++- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 1e90babbb3..5402cb3020 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -216,7 +216,7 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { source: .init(rawValue: context.source) ?? .ios, telemetry: .init( message: "[Mobile Metric] \(name)", - telemetryInfo: attributes.enrichIfNeeded(with: context) + telemetryInfo: attributes.enrichWithDeviceOsInfo(using: context) ), version: context.sdkVersion, view: rum?.viewID.map { .init(id: $0) } @@ -257,25 +257,21 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { } fileprivate extension [String: Encodable] { - func enrichIfNeeded( - with context: DatadogContext + func enrichWithDeviceOsInfo( + using context: DatadogContext ) -> [String: Encodable] { - if isMethodCallAttributes { - var attributes = self - attributes[MethodCalledMetric.Device.key] = [ - MethodCalledMetric.Device.model: context.device.model, - MethodCalledMetric.Device.brand: context.device.brand, - MethodCalledMetric.Device.architecture: context.device.architecture - ] - attributes[MethodCalledMetric.OS.key] = [ - MethodCalledMetric.OS.name: context.device.osName, - MethodCalledMetric.OS.version: context.device.osVersion, - MethodCalledMetric.OS.build: context.device.osBuildNumber, - ] - return attributes - } else { - return self - } + var attributes = self + attributes[MethodCalledMetric.Device.key] = [ + MethodCalledMetric.Device.model: context.device.model, + MethodCalledMetric.Device.brand: context.device.brand, + MethodCalledMetric.Device.architecture: context.device.architecture + ] + attributes[MethodCalledMetric.OS.key] = [ + MethodCalledMetric.OS.name: context.device.osName, + MethodCalledMetric.OS.version: context.device.osVersion, + MethodCalledMetric.OS.build: context.device.osBuildNumber, + ] + return attributes } } diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 8dfb2a28ed..197170eb4e 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -407,7 +407,7 @@ class TelemetryReceiverTests: XCTestCase { DDAssertReflectionEqual(event?.telemetry.telemetryInfo, randomAttributes) } - func testSendTelemetryMetricWithRUMContext() { + func testSendTelemetryMetricWithRUMContext() throws { // Given let rumContext: RUMCoreContext = .mockRandom() featureScope.contextMock.baggages = [RUMFeature.name: FeatureBaggage(rumContext)] @@ -422,6 +422,14 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.session?.id, rumContext.sessionID) XCTAssertEqual(event?.view?.id, rumContext.viewID) XCTAssertEqual(event?.action?.id, rumContext.userActionID) + let device = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.Device.key] as? [String: String]) + XCTAssertTrue(device[MethodCalledMetric.Device.model]?.isEmpty == false) + XCTAssertTrue(device[MethodCalledMetric.Device.brand]?.isEmpty == false) + XCTAssertTrue(device[MethodCalledMetric.Device.architecture]?.isEmpty == false) + let os = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.OS.key] as? [String: String]) + XCTAssertTrue(os[MethodCalledMetric.OS.version]?.isEmpty == false) + XCTAssertTrue(os[MethodCalledMetric.OS.build]?.isEmpty == false) + XCTAssertTrue(os[MethodCalledMetric.OS.name]?.isEmpty == false) } func testMethodCallTelemetryPropagetsAllData() throws { From f991a238b76000754a2aa1aaf4f995d868968841 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Fri, 24 May 2024 14:20:08 +0100 Subject: [PATCH 55/71] RUM-4133 Fix tests --- .../Integrations/TelemetryReceiverTests.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 197170eb4e..8ba1e7620e 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -379,7 +379,7 @@ class TelemetryReceiverTests: XCTestCase { // MARK: - Metrics Telemetry Events - func testSendTelemetryMetric() { + func testSendTelemetryMetric() throws { featureScope.contextMock = .mockWith( version: "app-version", source: "react-native", @@ -404,7 +404,17 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.service, "dd-sdk-ios") XCTAssertEqual(event?.source, .reactNative) XCTAssertEqual(event?.telemetry.message, "[Mobile Metric] \(randomName)") - DDAssertReflectionEqual(event?.telemetry.telemetryInfo, randomAttributes) + randomAttributes.forEach { key, value in + DDAssertReflectionEqual(event?.telemetry.telemetryInfo[key], value) + } + let device = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.Device.key] as? [String: String]) + XCTAssertTrue(device[MethodCalledMetric.Device.model]?.isEmpty == false) + XCTAssertTrue(device[MethodCalledMetric.Device.brand]?.isEmpty == false) + XCTAssertTrue(device[MethodCalledMetric.Device.architecture]?.isEmpty == false) + let os = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.OS.key] as? [String: String]) + XCTAssertTrue(os[MethodCalledMetric.OS.version]?.isEmpty == false) + XCTAssertTrue(os[MethodCalledMetric.OS.build]?.isEmpty == false) + XCTAssertTrue(os[MethodCalledMetric.OS.name]?.isEmpty == false) } func testSendTelemetryMetricWithRUMContext() throws { From da70fd02804a0ddeee68291c5311560c50d97dc2 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Mon, 27 May 2024 11:30:36 +0100 Subject: [PATCH 56/71] RUM-4133 Fix tests --- .../Public/CoreTelemetryIntegrationTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift b/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift index c527f02a9b..9a72b80ef5 100644 --- a/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift +++ b/Datadog/IntegrationUnitTests/Public/CoreTelemetryIntegrationTests.swift @@ -22,7 +22,7 @@ class CoreTelemetryIntegrationTests: XCTestCase { core = nil } - func testGivenRUMEnabled_telemetryEventsAreSent() { + func testGivenRUMEnabled_telemetryEventsAreSent() throws { // Given var config = RUM.Configuration(applicationID: .mockAny()) config.telemetrySampleRate = 100 @@ -57,7 +57,9 @@ class CoreTelemetryIntegrationTests: XCTestCase { let metric = debugEvents[1] XCTAssertEqual(metric.telemetry.message, "[Mobile Metric] Metric Name") - DDAssertReflectionEqual(metric.telemetry.telemetryInfo, ["metric.attribute": 42]) + + let metricAttribute = try XCTUnwrap(metric.telemetry.telemetryInfo["metric.attribute"] as? Int) + XCTAssertEqual(metricAttribute, 42) let methodCalledMetric = debugEvents[2] XCTAssertEqual(methodCalledMetric.telemetry.message, "[Mobile Metric] Method Called") From f4dcf50f9db97062900f584b30ab435e67813434 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Mon, 3 Jun 2024 17:25:27 +0200 Subject: [PATCH 57/71] RUM-4133 Update RUM Model --- .../Sources/RUM/RUMDataModels+objc.swift | 225 +++++++++++++++++- .../Sources/DataModels/RUMDataModels.swift | 191 ++++++++++++++- 2 files changed, 408 insertions(+), 8 deletions(-) diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index 3ca5d74aa9..283d4b9530 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -5292,6 +5292,10 @@ public class DDRUMViewEventView: NSObject { root.swiftModel.view.cumulativeLayoutShiftTargetSelector } + @objc public var cumulativeLayoutShiftTime: NSNumber? { + root.swiftModel.view.cumulativeLayoutShiftTime as NSNumber? + } + @objc public var customTimings: [String: NSNumber]? { root.swiftModel.view.customTimings as [String: NSNumber]? } @@ -5364,6 +5368,10 @@ public class DDRUMViewEventView: NSObject { root.swiftModel.view.interactionToNextPaintTargetSelector } + @objc public var interactionToNextPaintTime: NSNumber? { + root.swiftModel.view.interactionToNextPaintTime as NSNumber? + } + @objc public var isActive: NSNumber? { root.swiftModel.view.isActive as NSNumber? } @@ -5782,6 +5790,10 @@ public class DDRUMVitalEventDD: NSObject { @objc public var session: DDRUMVitalEventDDSession? { root.swiftModel.dd.session != nil ? DDRUMVitalEventDDSession(root: root) : nil } + + @objc public var vital: DDRUMVitalEventDDVital? { + root.swiftModel.dd.vital != nil ? DDRUMVitalEventDDVital(root: root) : nil + } } @objc @@ -5879,6 +5891,19 @@ public enum DDRUMVitalEventDDSessionRUMSessionPrecondition: Int { case explicitStop } +@objc +public class DDRUMVitalEventDDVital: NSObject { + internal let root: DDRUMVitalEvent + + internal init(root: DDRUMVitalEvent) { + self.root = root + } + + @objc public var computedValue: NSNumber? { + root.swiftModel.dd.vital!.computedValue as NSNumber? + } +} + @objc public class DDRUMVitalEventApplication: NSObject { internal let root: DDRUMVitalEvent @@ -6588,6 +6613,10 @@ public class DDTelemetryErrorEventTelemetry: NSObject { self.root = root } + @objc public var device: DDTelemetryErrorEventTelemetryRUMTelemetryDevice? { + root.swiftModel.telemetry.device != nil ? DDTelemetryErrorEventTelemetryRUMTelemetryDevice(root: root) : nil + } + @objc public var error: DDTelemetryErrorEventTelemetryError? { root.swiftModel.telemetry.error != nil ? DDTelemetryErrorEventTelemetryError(root: root) : nil } @@ -6596,6 +6625,10 @@ public class DDTelemetryErrorEventTelemetry: NSObject { root.swiftModel.telemetry.message } + @objc public var os: DDTelemetryErrorEventTelemetryRUMTelemetryOperatingSystem? { + root.swiftModel.telemetry.os != nil ? DDTelemetryErrorEventTelemetryRUMTelemetryOperatingSystem(root: root) : nil + } + @objc public var status: String { root.swiftModel.telemetry.status } @@ -6603,6 +6636,31 @@ public class DDTelemetryErrorEventTelemetry: NSObject { @objc public var type: String? { root.swiftModel.telemetry.type } + + @objc public var telemetryInfo: [String: Any] { + root.swiftModel.telemetry.telemetryInfo.castToObjectiveC() + } +} + +@objc +public class DDTelemetryErrorEventTelemetryRUMTelemetryDevice: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var architecture: String? { + root.swiftModel.telemetry.device!.architecture + } + + @objc public var brand: String? { + root.swiftModel.telemetry.device!.brand + } + + @objc public var model: String? { + root.swiftModel.telemetry.device!.model + } } @objc @@ -6622,6 +6680,27 @@ public class DDTelemetryErrorEventTelemetryError: NSObject { } } +@objc +public class DDTelemetryErrorEventTelemetryRUMTelemetryOperatingSystem: NSObject { + internal let root: DDTelemetryErrorEvent + + internal init(root: DDTelemetryErrorEvent) { + self.root = root + } + + @objc public var build: String? { + root.swiftModel.telemetry.os!.build + } + + @objc public var name: String? { + root.swiftModel.telemetry.os!.name + } + + @objc public var version: String? { + root.swiftModel.telemetry.os!.version + } +} + @objc public class DDTelemetryErrorEventView: NSObject { internal let root: DDTelemetryErrorEvent @@ -6785,10 +6864,18 @@ public class DDTelemetryDebugEventTelemetry: NSObject { self.root = root } + @objc public var device: DDTelemetryDebugEventTelemetryRUMTelemetryDevice? { + root.swiftModel.telemetry.device != nil ? DDTelemetryDebugEventTelemetryRUMTelemetryDevice(root: root) : nil + } + @objc public var message: String { root.swiftModel.telemetry.message } + @objc public var os: DDTelemetryDebugEventTelemetryRUMTelemetryOperatingSystem? { + root.swiftModel.telemetry.os != nil ? DDTelemetryDebugEventTelemetryRUMTelemetryOperatingSystem(root: root) : nil + } + @objc public var status: String { root.swiftModel.telemetry.status } @@ -6802,6 +6889,48 @@ public class DDTelemetryDebugEventTelemetry: NSObject { } } +@objc +public class DDTelemetryDebugEventTelemetryRUMTelemetryDevice: NSObject { + internal let root: DDTelemetryDebugEvent + + internal init(root: DDTelemetryDebugEvent) { + self.root = root + } + + @objc public var architecture: String? { + root.swiftModel.telemetry.device!.architecture + } + + @objc public var brand: String? { + root.swiftModel.telemetry.device!.brand + } + + @objc public var model: String? { + root.swiftModel.telemetry.device!.model + } +} + +@objc +public class DDTelemetryDebugEventTelemetryRUMTelemetryOperatingSystem: NSObject { + internal let root: DDTelemetryDebugEvent + + internal init(root: DDTelemetryDebugEvent) { + self.root = root + } + + @objc public var build: String? { + root.swiftModel.telemetry.os!.build + } + + @objc public var name: String? { + root.swiftModel.telemetry.os!.name + } + + @objc public var version: String? { + root.swiftModel.telemetry.os!.version + } +} + @objc public class DDTelemetryDebugEventView: NSObject { internal let root: DDTelemetryDebugEvent @@ -6969,9 +7098,21 @@ public class DDTelemetryConfigurationEventTelemetry: NSObject { DDTelemetryConfigurationEventTelemetryConfiguration(root: root) } + @objc public var device: DDTelemetryConfigurationEventTelemetryRUMTelemetryDevice? { + root.swiftModel.telemetry.device != nil ? DDTelemetryConfigurationEventTelemetryRUMTelemetryDevice(root: root) : nil + } + + @objc public var os: DDTelemetryConfigurationEventTelemetryRUMTelemetryOperatingSystem? { + root.swiftModel.telemetry.os != nil ? DDTelemetryConfigurationEventTelemetryRUMTelemetryOperatingSystem(root: root) : nil + } + @objc public var type: String { root.swiftModel.telemetry.type } + + @objc public var telemetryInfo: [String: Any] { + root.swiftModel.telemetry.telemetryInfo.castToObjectiveC() + } } @objc @@ -7028,6 +7169,11 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { get { root.swiftModel.telemetry.configuration.defaultPrivacyLevel } } + @objc public var enablePrivacyForActionName: NSNumber? { + set { root.swiftModel.telemetry.configuration.enablePrivacyForActionName = newValue?.boolValue } + get { root.swiftModel.telemetry.configuration.enablePrivacyForActionName as NSNumber? } + } + @objc public var forwardConsoleLogs: DDTelemetryConfigurationEventTelemetryConfigurationForwardConsoleLogs? { root.swiftModel.telemetry.configuration.forwardConsoleLogs != nil ? DDTelemetryConfigurationEventTelemetryConfigurationForwardConsoleLogs(root: root) : nil } @@ -7072,6 +7218,11 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { root.swiftModel.telemetry.configuration.selectedTracingPropagators?.map { DDTelemetryConfigurationEventTelemetryConfigurationSelectedTracingPropagators(swift: $0).rawValue } } + @objc public var sendLogsAfterSessionExpiration: NSNumber? { + set { root.swiftModel.telemetry.configuration.sendLogsAfterSessionExpiration = newValue?.boolValue } + get { root.swiftModel.telemetry.configuration.sendLogsAfterSessionExpiration as NSNumber? } + } + @objc public var sessionReplaySampleRate: NSNumber? { set { root.swiftModel.telemetry.configuration.sessionReplaySampleRate = newValue?.int64Value } get { root.swiftModel.telemetry.configuration.sessionReplaySampleRate as NSNumber? } @@ -7199,8 +7350,8 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject { get { root.swiftModel.telemetry.configuration.trackViewsManually as NSNumber? } } - @objc public var trackingConsent: String? { - root.swiftModel.telemetry.configuration.trackingConsent + @objc public var trackingConsent: DDTelemetryConfigurationEventTelemetryConfigurationTrackingConsent { + .init(swift: root.swiftModel.telemetry.configuration.trackingConsent) } @objc public var unityVersion: String? { @@ -7366,6 +7517,32 @@ public enum DDTelemetryConfigurationEventTelemetryConfigurationTraceContextInjec case sampled } +@objc +public enum DDTelemetryConfigurationEventTelemetryConfigurationTrackingConsent: Int { + internal init(swift: TelemetryConfigurationEvent.Telemetry.Configuration.TrackingConsent?) { + switch swift { + case nil: self = .none + case .granted?: self = .granted + case .notGranted?: self = .notGranted + case .pending?: self = .pending + } + } + + internal var toSwift: TelemetryConfigurationEvent.Telemetry.Configuration.TrackingConsent? { + switch self { + case .none: return nil + case .granted: return .granted + case .notGranted: return .notGranted + case .pending: return .pending + } + } + + case none + case granted + case notGranted + case pending +} + @objc public enum DDTelemetryConfigurationEventTelemetryConfigurationViewTrackingStrategy: Int { internal init(swift: TelemetryConfigurationEvent.Telemetry.Configuration.ViewTrackingStrategy?) { @@ -7395,6 +7572,48 @@ public enum DDTelemetryConfigurationEventTelemetryConfigurationViewTrackingStrat case navigationViewTrackingStrategy } +@objc +public class DDTelemetryConfigurationEventTelemetryRUMTelemetryDevice: NSObject { + internal let root: DDTelemetryConfigurationEvent + + internal init(root: DDTelemetryConfigurationEvent) { + self.root = root + } + + @objc public var architecture: String? { + root.swiftModel.telemetry.device!.architecture + } + + @objc public var brand: String? { + root.swiftModel.telemetry.device!.brand + } + + @objc public var model: String? { + root.swiftModel.telemetry.device!.model + } +} + +@objc +public class DDTelemetryConfigurationEventTelemetryRUMTelemetryOperatingSystem: NSObject { + internal let root: DDTelemetryConfigurationEvent + + internal init(root: DDTelemetryConfigurationEvent) { + self.root = root + } + + @objc public var build: String? { + root.swiftModel.telemetry.os!.build + } + + @objc public var name: String? { + root.swiftModel.telemetry.os!.name + } + + @objc public var version: String? { + root.swiftModel.telemetry.os!.version + } +} + @objc public class DDTelemetryConfigurationEventView: NSObject { internal let root: DDTelemetryConfigurationEvent @@ -7410,4 +7629,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/0455e104863c0f67c3bf69899c7d5da1ba6f0ebb +// Generated from https://github.com/DataDog/rum-events-format/tree/30d4b773abb4e33edc9d6053d3c12cd302e948a5 diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index e9dc6dfe1c..c6ee571394 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -2326,6 +2326,9 @@ public struct RUMViewEvent: RUMDataModel { /// CSS selector path of the first element (in document order) of the largest layout shift contributing to CLS public let cumulativeLayoutShiftTargetSelector: String? + /// Duration in ns between start of the view and start of the largest layout shift contributing to CLS + public let cumulativeLayoutShiftTime: Int64? + /// User custom timings of the view. As timing name is used as facet path, it must contain only letters, digits, or the characters - _ . @ $ public let customTimings: [String: Int64]? @@ -2380,6 +2383,9 @@ public struct RUMViewEvent: RUMDataModel { /// CSS selector path of the interacted element corresponding to INP public let interactionToNextPaintTargetSelector: String? + /// Duration in ns between start of the view and start of the INP + public let interactionToNextPaintTime: Int64? + /// Whether the View corresponding to this event is considered active public let isActive: Bool? @@ -2441,6 +2447,7 @@ public struct RUMViewEvent: RUMDataModel { case crash = "crash" case cumulativeLayoutShift = "cumulative_layout_shift" case cumulativeLayoutShiftTargetSelector = "cumulative_layout_shift_target_selector" + case cumulativeLayoutShiftTime = "cumulative_layout_shift_time" case customTimings = "custom_timings" case domComplete = "dom_complete" case domContentLoaded = "dom_content_loaded" @@ -2459,6 +2466,7 @@ public struct RUMViewEvent: RUMDataModel { case inForegroundPeriods = "in_foreground_periods" case interactionToNextPaint = "interaction_to_next_paint" case interactionToNextPaintTargetSelector = "interaction_to_next_paint_target_selector" + case interactionToNextPaintTime = "interaction_to_next_paint_time" case isActive = "is_active" case isSlowRendered = "is_slow_rendered" case jsRefreshRate = "js_refresh_rate" @@ -2770,11 +2778,15 @@ public struct RUMVitalEvent: RUMDataModel { /// Session-related internal properties public let session: Session? + /// Internal vital properties + public let vital: Vital? + enum CodingKeys: String, CodingKey { case browserSdkVersion = "browser_sdk_version" case configuration = "configuration" case formatVersion = "format_version" case session = "session" + case vital = "vital" } /// Subset of the SDK configuration options in use during its execution @@ -2810,6 +2822,16 @@ public struct RUMVitalEvent: RUMDataModel { case plan2 = 2 } } + + /// Internal vital properties + public struct Vital: Codable { + /// Whether the value of the vital is computed by the SDK (as opposed to directly provided by the customer) + public let computedValue: Bool? + + enum CodingKeys: String, CodingKey { + case computedValue = "computed_value" + } + } } /// Application properties @@ -2987,7 +3009,7 @@ public struct TelemetryErrorEvent: RUMDataModel { public let source: Source /// The telemetry log information - public let telemetry: Telemetry + public internal(set) var telemetry: Telemetry /// Telemetry event type. Should specify telemetry only. public let type: String = "telemetry" @@ -3065,21 +3087,31 @@ public struct TelemetryErrorEvent: RUMDataModel { /// The telemetry log information public struct Telemetry: Codable { + /// Device properties + public let device: RUMTelemetryDevice? + /// Error properties public let error: Error? /// Body of the log public let message: String + /// OS properties + public let os: RUMTelemetryOperatingSystem? + /// Level/severity of the log public let status: String = "error" /// Telemetry type public let type: String? = "log" - enum CodingKeys: String, CodingKey { + public internal(set) var telemetryInfo: [String: Encodable] + + enum StaticCodingKeys: String, CodingKey { + case device = "device" case error = "error" case message = "message" + case os = "os" case status = "status" case type = "type" } @@ -3110,6 +3142,45 @@ public struct TelemetryErrorEvent: RUMDataModel { } } +extension TelemetryErrorEvent.Telemetry { + public func encode(to encoder: Encoder) throws { + // Encode static properties: + var staticContainer = encoder.container(keyedBy: StaticCodingKeys.self) + try staticContainer.encodeIfPresent(device, forKey: .device) + try staticContainer.encodeIfPresent(error, forKey: .error) + try staticContainer.encodeIfPresent(message, forKey: .message) + try staticContainer.encodeIfPresent(os, forKey: .os) + + // Encode dynamic properties: + var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self) + try telemetryInfo.forEach { + let key = DynamicCodingKey($0) + try dynamicContainer.encode(AnyEncodable($1), forKey: key) + } + } + + public init(from decoder: Decoder) throws { + // Decode static properties: + let staticContainer = try decoder.container(keyedBy: StaticCodingKeys.self) + self.device = try staticContainer.decodeIfPresent(RUMTelemetryDevice.self, forKey: .device) + self.error = try staticContainer.decodeIfPresent(Error.self, forKey: .error) + self.message = try staticContainer.decode(String.self, forKey: .message) + self.os = try staticContainer.decodeIfPresent(RUMTelemetryOperatingSystem.self, forKey: .os) + + // Decode other properties into [String: Codable] dictionary: + let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) + let allStaticKeys = Set(staticContainer.allKeys.map { $0.stringValue }) + let dynamicKeys = dynamicContainer.allKeys.filter { !allStaticKeys.contains($0.stringValue) } + var dictionary: [String: Codable] = [:] + + try dynamicKeys.forEach { codingKey in + dictionary[codingKey.stringValue] = try dynamicContainer.decode(AnyCodable.self, forKey: codingKey) + } + + self.telemetryInfo = dictionary + } +} + /// Schema of all properties of a telemetry debug event public struct TelemetryDebugEvent: RUMDataModel { /// Internal properties @@ -3215,9 +3286,15 @@ public struct TelemetryDebugEvent: RUMDataModel { /// The telemetry log information public struct Telemetry: Codable { + /// Device properties + public let device: RUMTelemetryDevice? + /// Body of the log public let message: String + /// OS properties + public let os: RUMTelemetryOperatingSystem? + /// Level/severity of the log public let status: String = "debug" @@ -3227,7 +3304,9 @@ public struct TelemetryDebugEvent: RUMDataModel { public internal(set) var telemetryInfo: [String: Encodable] enum StaticCodingKeys: String, CodingKey { + case device = "device" case message = "message" + case os = "os" case status = "status" case type = "type" } @@ -3248,7 +3327,9 @@ extension TelemetryDebugEvent.Telemetry { public func encode(to encoder: Encoder) throws { // Encode static properties: var staticContainer = encoder.container(keyedBy: StaticCodingKeys.self) + try staticContainer.encodeIfPresent(device, forKey: .device) try staticContainer.encodeIfPresent(message, forKey: .message) + try staticContainer.encodeIfPresent(os, forKey: .os) // Encode dynamic properties: var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self) @@ -3261,7 +3342,9 @@ extension TelemetryDebugEvent.Telemetry { public init(from decoder: Decoder) throws { // Decode static properties: let staticContainer = try decoder.container(keyedBy: StaticCodingKeys.self) + self.device = try staticContainer.decodeIfPresent(RUMTelemetryDevice.self, forKey: .device) self.message = try staticContainer.decode(String.self, forKey: .message) + self.os = try staticContainer.decodeIfPresent(RUMTelemetryOperatingSystem.self, forKey: .os) // Decode other properties into [String: Codable] dictionary: let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) @@ -3385,11 +3468,21 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Configuration properties public var configuration: Configuration + /// Device properties + public let device: RUMTelemetryDevice? + + /// OS properties + public let os: RUMTelemetryOperatingSystem? + /// Telemetry type public let type: String = "configuration" - enum CodingKeys: String, CodingKey { + public internal(set) var telemetryInfo: [String: Encodable] + + enum StaticCodingKeys: String, CodingKey { case configuration = "configuration" + case device = "device" + case os = "os" case type = "type" } @@ -3428,6 +3521,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// Session replay default privacy level public var defaultPrivacyLevel: String? + /// Privacy control for action name + public var enablePrivacyForActionName: Bool? + /// The console.* tracked public let forwardConsoleLogs: ForwardConsoleLogs? @@ -3458,6 +3554,9 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// A list of selected tracing propagators public let selectedTracingPropagators: [SelectedTracingPropagators]? + /// Whether logs are sent after the session expiration + public var sendLogsAfterSessionExpiration: Bool? + /// The percentage of sessions with RUM & Session Replay pricing tracked public var sessionReplaySampleRate: Int64? @@ -3540,7 +3639,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { public var trackViewsManually: Bool? /// The initial tracking consent value - public let trackingConsent: String? + public let trackingConsent: TrackingConsent? /// The version of Unity used in a Unity application public var unityVersion: String? @@ -3599,6 +3698,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case compressIntakeRequests = "compress_intake_requests" case dartVersion = "dart_version" case defaultPrivacyLevel = "default_privacy_level" + case enablePrivacyForActionName = "enable_privacy_for_action_name" case forwardConsoleLogs = "forward_console_logs" case forwardErrorsToLogs = "forward_errors_to_logs" case forwardReports = "forward_reports" @@ -3609,6 +3709,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case reactVersion = "react_version" case replaySampleRate = "replay_sample_rate" case selectedTracingPropagators = "selected_tracing_propagators" + case sendLogsAfterSessionExpiration = "send_logs_after_session_expiration" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" case silentMultipleInit = "silent_multiple_init" @@ -3751,6 +3852,13 @@ public struct TelemetryConfigurationEvent: RUMDataModel { case sampled = "sampled" } + /// The initial tracking consent value + public enum TrackingConsent: String, Codable { + case granted = "granted" + case notGranted = "not-granted" + case pending = "pending" + } + /// View tracking strategy public enum ViewTrackingStrategy: String, Codable { case activityViewTrackingStrategy = "ActivityViewTrackingStrategy" @@ -3772,6 +3880,43 @@ public struct TelemetryConfigurationEvent: RUMDataModel { } } +extension TelemetryConfigurationEvent.Telemetry { + public func encode(to encoder: Encoder) throws { + // Encode static properties: + var staticContainer = encoder.container(keyedBy: StaticCodingKeys.self) + try staticContainer.encodeIfPresent(configuration, forKey: .configuration) + try staticContainer.encodeIfPresent(device, forKey: .device) + try staticContainer.encodeIfPresent(os, forKey: .os) + + // Encode dynamic properties: + var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self) + try telemetryInfo.forEach { + let key = DynamicCodingKey($0) + try dynamicContainer.encode(AnyEncodable($1), forKey: key) + } + } + + public init(from decoder: Decoder) throws { + // Decode static properties: + let staticContainer = try decoder.container(keyedBy: StaticCodingKeys.self) + self.configuration = try staticContainer.decode(Configuration.self, forKey: .configuration) + self.device = try staticContainer.decodeIfPresent(RUMTelemetryDevice.self, forKey: .device) + self.os = try staticContainer.decodeIfPresent(RUMTelemetryOperatingSystem.self, forKey: .os) + + // Decode other properties into [String: Codable] dictionary: + let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) + let allStaticKeys = Set(staticContainer.allKeys.map { $0.stringValue }) + let dynamicKeys = dynamicContainer.allKeys.filter { !allStaticKeys.contains($0.stringValue) } + var dictionary: [String: Codable] = [:] + + try dynamicKeys.forEach { codingKey in + dictionary[codingKey.stringValue] = try dynamicContainer.decode(AnyCodable.self, forKey: codingKey) + } + + self.telemetryInfo = dictionary + } +} + /// The precondition that led to the creation of the session public enum RUMSessionPrecondition: String, Codable { case userAppLaunch = "user_app_launch" @@ -4081,4 +4226,40 @@ public enum RUMMethod: String, Codable { case connect = "CONNECT" } -// Generated from https://github.com/DataDog/rum-events-format/tree/0455e104863c0f67c3bf69899c7d5da1ba6f0ebb +/// Device properties +public struct RUMTelemetryDevice: Codable { + /// Architecture of the device + public let architecture: String? + + /// Brand of the device + public let brand: String? + + /// Model of the device + public let model: String? + + enum CodingKeys: String, CodingKey { + case architecture = "architecture" + case brand = "brand" + case model = "model" + } +} + +/// OS properties +public struct RUMTelemetryOperatingSystem: Codable { + /// Build of the OS + public let build: String? + + /// Name of the OS + public let name: String? + + /// Version of the OS + public let version: String? + + enum CodingKeys: String, CodingKey { + case build = "build" + case name = "name" + case version = "version" + } +} + +// Generated from https://github.com/DataDog/rum-events-format/tree/30d4b773abb4e33edc9d6053d3c12cd302e948a5 From 7901da1762c2b468a8a722d086c35e4f2ecc1595 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 4 Jun 2024 13:20:16 +0200 Subject: [PATCH 58/71] RUM-4133 Update receiver to propagate device data to all metrics --- .../Datadog/Mocks/RUMDataModelMocks.swift | 27 ++++++++- .../SDKMetrics/MethodCalledMetric.swift | 31 ---------- DatadogRUM/Sources/FatalErrorBuilder.swift | 2 + .../Integrations/CrashReportReceiver.swift | 2 + .../Integrations/TelemetryReceiver.swift | 60 ++++++++++++------- .../RUMMonitor/Scopes/RUMViewScope.swift | 2 + .../Integrations/TelemetryReceiverTests.swift | 48 +++++++-------- .../Tests/Mocks/RUMDataModelMocks.swift | 27 ++++++++- 8 files changed, 120 insertions(+), 79 deletions(-) diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift index cbc9b059bc..3505464f39 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift @@ -165,6 +165,7 @@ extension RUMViewEvent: RandomMockable { crash: crashCount.map { .init(count: $0) }, cumulativeLayoutShift: .mockRandom(), cumulativeLayoutShiftTargetSelector: nil, + cumulativeLayoutShiftTime: .mockRandom(), customTimings: .mockAny(), domComplete: .mockRandom(), domContentLoaded: .mockRandom(), @@ -188,6 +189,7 @@ extension RUMViewEvent: RandomMockable { ], interactionToNextPaint: nil, interactionToNextPaintTargetSelector: nil, + interactionToNextPaintTime: .mockRandom(), isActive: viewIsActive, isSlowRendered: .mockRandom(), jsRefreshRate: nil, @@ -553,10 +555,33 @@ extension TelemetryConfigurationEvent: RandomMockable { useTracing: .mockRandom(), useWorkerUrl: nil, viewTrackingStrategy: nil - ) + ), + device: .mockRandom(), + os: .mockRandom(), + telemetryInfo: [:] ), version: .mockAny(), view: .init(id: .mockRandom()) ) } } + +extension RUMTelemetryDevice: RandomMockable { + public static func mockRandom() -> RUMTelemetryDevice { + return RUMTelemetryDevice( + architecture: .mockRandom(), + brand: .mockRandom(), + model: .mockRandom() + ) + } +} + +extension RUMTelemetryOperatingSystem: RandomMockable { + public static func mockRandom() -> RUMTelemetryOperatingSystem { + return RUMTelemetryOperatingSystem( + build: .mockRandom(), + name: .mockRandom(), + version: .mockRandom() + ) + } +} diff --git a/DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift b/DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift index 2d17c5c822..569415c7cb 100644 --- a/DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift +++ b/DatadogInternal/Sources/SDKMetrics/MethodCalledMetric.swift @@ -22,35 +22,4 @@ public enum MethodCalledMetric { public static let isSuccessful = "is_successful" /// The key for execution time. public static let executionTime = "execution_time" - - public enum Device { - /// The key for device object. - public static let key = "device" - - /// The key for device model name. - public static let model = "model" - /// The key for device brand. - public static let brand = "brand" - /// The key for CPU architecture. - public static let architecture = "architecture" - } - - /// The key for OS object. - public enum OS { - /// The key for operating system object. - public static let key = "os" - - /// The key for OS name. - public static let name = "name" - /// The key for OS version. - public static let version = "version" - /// The key for OS build. - public static let build = "build" - } -} - -public extension [String: Encodable] { - var isMethodCallAttributes: Bool { - self[SDKMetricFields.typeKey] as? String == MethodCalledMetric.typeValue - } } diff --git a/DatadogRUM/Sources/FatalErrorBuilder.swift b/DatadogRUM/Sources/FatalErrorBuilder.swift index 98911fa78c..be78a263a7 100644 --- a/DatadogRUM/Sources/FatalErrorBuilder.swift +++ b/DatadogRUM/Sources/FatalErrorBuilder.swift @@ -167,6 +167,7 @@ internal struct FatalErrorBuilder { ), cumulativeLayoutShift: original.view.cumulativeLayoutShift, cumulativeLayoutShiftTargetSelector: original.view.cumulativeLayoutShiftTargetSelector, + cumulativeLayoutShiftTime: original.view.cumulativeLayoutShiftTime, customTimings: original.view.customTimings, domComplete: original.view.domComplete, domContentLoaded: original.view.domContentLoaded, @@ -187,6 +188,7 @@ internal struct FatalErrorBuilder { inForegroundPeriods: original.view.inForegroundPeriods, interactionToNextPaint: original.view.interactionToNextPaint, interactionToNextPaintTargetSelector: original.view.interactionToNextPaintTargetSelector, + interactionToNextPaintTime: original.view.interactionToNextPaintTime, isActive: false, // after fatal error, this is no longer active view isSlowRendered: original.view.isSlowRendered, jsRefreshRate: original.view.jsRefreshRate, diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 1af52bc244..8a781b561c 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -417,6 +417,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { crash: .init(count: 0), cumulativeLayoutShift: nil, cumulativeLayoutShiftTargetSelector: nil, + cumulativeLayoutShiftTime: nil, customTimings: nil, domComplete: nil, domContentLoaded: nil, @@ -435,6 +436,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { inForegroundPeriods: nil, interactionToNextPaint: nil, interactionToNextPaintTargetSelector: nil, + interactionToNextPaintTime: nil, isActive: false, // we know it won't receive updates isSlowRendered: false, jsRefreshRate: nil, diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 5402cb3020..a7349c16fd 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -115,7 +115,9 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { session: rum.map { .init(id: $0.sessionID) }, source: .init(rawValue: context.source) ?? .ios, telemetry: .init( + device: .init(context.device), message: message, + os: .init(context.device), telemetryInfo: attributes ?? [:] ), version: context.sdkVersion, @@ -152,7 +154,13 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { service: "dd-sdk-ios", session: rum.map { .init(id: $0.sessionID) }, source: .init(rawValue: context.source) ?? .ios, - telemetry: .init(error: .init(kind: kind, stack: stack), message: message), + telemetry: .init( + device: .init(context.device), + error: .init(kind: kind, stack: stack), + message: message, + os: .init(context.device), + telemetryInfo: [:] + ), version: context.sdkVersion, view: rum?.viewID.map { .init(id: $0) } ) @@ -186,7 +194,12 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { service: "dd-sdk-ios", session: rum.map { .init(id: $0.sessionID) }, source: .init(rawValue: context.source) ?? .ios, - telemetry: .init(configuration: .init(configuration)), + telemetry: .init( + configuration: .init(configuration), + device: .init(context.device), + os: .init(context.device), + telemetryInfo: [:] + ), version: context.sdkVersion, view: rum?.viewID.map { .init(id: $0) } ) @@ -215,8 +228,10 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { session: rum.map { .init(id: $0.sessionID) }, source: .init(rawValue: context.source) ?? .ios, telemetry: .init( + device: .init(context.device), message: "[Mobile Metric] \(name)", - telemetryInfo: attributes.enrichWithDeviceOsInfo(using: context) + os: .init(context.device), + telemetryInfo: attributes ), version: context.sdkVersion, view: rum?.viewID.map { .init(id: $0) } @@ -256,25 +271,6 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { } } -fileprivate extension [String: Encodable] { - func enrichWithDeviceOsInfo( - using context: DatadogContext - ) -> [String: Encodable] { - var attributes = self - attributes[MethodCalledMetric.Device.key] = [ - MethodCalledMetric.Device.model: context.device.model, - MethodCalledMetric.Device.brand: context.device.brand, - MethodCalledMetric.Device.architecture: context.device.architecture - ] - attributes[MethodCalledMetric.OS.key] = [ - MethodCalledMetric.OS.name: context.device.osName, - MethodCalledMetric.OS.version: context.device.osVersion, - MethodCalledMetric.OS.build: context.device.osBuildNumber, - ] - return attributes - } -} - private extension TelemetryConfigurationEvent.Telemetry.Configuration { init(_ configuration: DatadogInternal.ConfigurationTelemetry) { self.init( @@ -344,3 +340,23 @@ private extension TelemetryConfigurationEvent.Telemetry.Configuration { ) } } + +fileprivate extension RUMTelemetryDevice { + init(_ device: DeviceInfo) { + self.init( + architecture: device.architecture, + brand: device.brand, + model: device.model + ) + } +} + +fileprivate extension RUMTelemetryOperatingSystem { + init(_ device: DeviceInfo) { + self.init( + build: device.osBuildNumber, + name: device.osName, + version: device.osVersion + ) + } +} diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index bca5bb5ca4..abe2b739b7 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -503,6 +503,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { crash: isCrash ? .init(count: 1) : .init(count: 0), cumulativeLayoutShift: nil, cumulativeLayoutShiftTargetSelector: nil, + cumulativeLayoutShiftTime: nil, customTimings: customTimings.reduce(into: [:]) { acc, element in acc[sanitizeCustomTimingName(customTiming: element.key)] = element.value }, @@ -523,6 +524,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { inForegroundPeriods: nil, interactionToNextPaint: nil, interactionToNextPaintTargetSelector: nil, + interactionToNextPaintTime: nil, isActive: isActive, isSlowRendered: isSlowRendered ?? false, jsRefreshRate: viewPerformanceMetrics[.jsFrameTimeSeconds]?.asJsRefreshRate(), diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 8ba1e7620e..703d83c26c 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -407,14 +407,14 @@ class TelemetryReceiverTests: XCTestCase { randomAttributes.forEach { key, value in DDAssertReflectionEqual(event?.telemetry.telemetryInfo[key], value) } - let device = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.Device.key] as? [String: String]) - XCTAssertTrue(device[MethodCalledMetric.Device.model]?.isEmpty == false) - XCTAssertTrue(device[MethodCalledMetric.Device.brand]?.isEmpty == false) - XCTAssertTrue(device[MethodCalledMetric.Device.architecture]?.isEmpty == false) - let os = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.OS.key] as? [String: String]) - XCTAssertTrue(os[MethodCalledMetric.OS.version]?.isEmpty == false) - XCTAssertTrue(os[MethodCalledMetric.OS.build]?.isEmpty == false) - XCTAssertTrue(os[MethodCalledMetric.OS.name]?.isEmpty == false) + let device = try XCTUnwrap(event?.telemetry.device) + XCTAssertTrue(device.model?.isEmpty == false) + XCTAssertTrue(device.brand?.isEmpty == false) + XCTAssertTrue(device.architecture?.isEmpty == false) + let os = try XCTUnwrap(event?.telemetry.os) + XCTAssertTrue(os.version?.isEmpty == false) + XCTAssertTrue(os.name?.isEmpty == false) + XCTAssertTrue(os.build?.isEmpty == false) } func testSendTelemetryMetricWithRUMContext() throws { @@ -432,14 +432,14 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.session?.id, rumContext.sessionID) XCTAssertEqual(event?.view?.id, rumContext.viewID) XCTAssertEqual(event?.action?.id, rumContext.userActionID) - let device = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.Device.key] as? [String: String]) - XCTAssertTrue(device[MethodCalledMetric.Device.model]?.isEmpty == false) - XCTAssertTrue(device[MethodCalledMetric.Device.brand]?.isEmpty == false) - XCTAssertTrue(device[MethodCalledMetric.Device.architecture]?.isEmpty == false) - let os = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.OS.key] as? [String: String]) - XCTAssertTrue(os[MethodCalledMetric.OS.version]?.isEmpty == false) - XCTAssertTrue(os[MethodCalledMetric.OS.build]?.isEmpty == false) - XCTAssertTrue(os[MethodCalledMetric.OS.name]?.isEmpty == false) + let device = try XCTUnwrap(event?.telemetry.device) + XCTAssertTrue(device.model?.isEmpty == false) + XCTAssertTrue(device.brand?.isEmpty == false) + XCTAssertTrue(device.architecture?.isEmpty == false) + let os = try XCTUnwrap(event?.telemetry.os) + XCTAssertTrue(os.version?.isEmpty == false) + XCTAssertTrue(os.name?.isEmpty == false) + XCTAssertTrue(os.build?.isEmpty == false) } func testMethodCallTelemetryPropagetsAllData() throws { @@ -466,14 +466,14 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.callerClass] as? String), callerClass) XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.isSuccessful] as? Bool), isSuccessful) XCTAssertGreaterThan(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.executionTime] as? Int64), 0) - let device = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.Device.key] as? [String: String]) - XCTAssertTrue(device[MethodCalledMetric.Device.model]?.isEmpty == false) - XCTAssertTrue(device[MethodCalledMetric.Device.brand]?.isEmpty == false) - XCTAssertTrue(device[MethodCalledMetric.Device.architecture]?.isEmpty == false) - let os = try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.OS.key] as? [String: String]) - XCTAssertTrue(os[MethodCalledMetric.OS.version]?.isEmpty == false) - XCTAssertTrue(os[MethodCalledMetric.OS.build]?.isEmpty == false) - XCTAssertTrue(os[MethodCalledMetric.OS.name]?.isEmpty == false) + let device = try XCTUnwrap(event?.telemetry.device) + XCTAssertTrue(device.model?.isEmpty == false) + XCTAssertTrue(device.brand?.isEmpty == false) + XCTAssertTrue(device.architecture?.isEmpty == false) + let os = try XCTUnwrap(event?.telemetry.os) + XCTAssertTrue(os.version?.isEmpty == false) + XCTAssertTrue(os.name?.isEmpty == false) + XCTAssertTrue(os.build?.isEmpty == false) } func testMethodCallTelemetryDroppedWhenSampledOut() { diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index ab08fe5a1b..da7a8ff6d4 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -178,6 +178,7 @@ extension RUMViewEvent: RandomMockable { crash: crashCount.map { .init(count: $0) }, cumulativeLayoutShift: .mockRandom(), cumulativeLayoutShiftTargetSelector: nil, + cumulativeLayoutShiftTime: .mockRandom(), customTimings: .mockAny(), domComplete: .mockRandom(), domContentLoaded: .mockRandom(), @@ -201,6 +202,7 @@ extension RUMViewEvent: RandomMockable { ], interactionToNextPaint: nil, interactionToNextPaintTargetSelector: nil, + interactionToNextPaintTime: .mockRandom(), isActive: viewIsActive, isSlowRendered: .mockRandom(), jsRefreshRate: nil, @@ -566,10 +568,33 @@ extension TelemetryConfigurationEvent: RandomMockable { useTracing: .mockRandom(), useWorkerUrl: nil, viewTrackingStrategy: nil - ) + ), + device: .mockRandom(), + os: .mockRandom(), + telemetryInfo: [:] ), version: .mockAny(), view: .init(id: .mockRandom()) ) } } + +extension RUMTelemetryDevice: RandomMockable { + public static func mockRandom() -> RUMTelemetryDevice { + return RUMTelemetryDevice( + architecture: .mockRandom(), + brand: .mockRandom(), + model: .mockRandom() + ) + } +} + +extension RUMTelemetryOperatingSystem: RandomMockable { + public static func mockRandom() -> RUMTelemetryOperatingSystem { + return RUMTelemetryOperatingSystem( + build: .mockRandom(), + name: .mockRandom(), + version: .mockRandom() + ) + } +} From 0f38f61228c9d1fa29e1c30ffd1ca1f799a3e771 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Tue, 4 Jun 2024 13:27:34 +0200 Subject: [PATCH 59/71] RUM-4133 Lint sources --- DatadogRUM/Sources/Integrations/TelemetryReceiver.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index a7349c16fd..1f9858be6a 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -155,7 +155,7 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { session: rum.map { .init(id: $0.sessionID) }, source: .init(rawValue: context.source) ?? .ios, telemetry: .init( - device: .init(context.device), + device: .init(context.device), error: .init(kind: kind, stack: stack), message: message, os: .init(context.device), @@ -228,9 +228,9 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { session: rum.map { .init(id: $0.sessionID) }, source: .init(rawValue: context.source) ?? .ios, telemetry: .init( - device: .init(context.device), + device: .init(context.device), message: "[Mobile Metric] \(name)", - os: .init(context.device), + os: .init(context.device), telemetryInfo: attributes ), version: context.sdkVersion, From a7f8a5943d7810324a05873bd8b7c0ae3cbacf81 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 12 Jun 2024 10:16:12 +0100 Subject: [PATCH 60/71] RUM-4133 Improve tests --- .../Integrations/TelemetryReceiverTests.swift | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 703d83c26c..829ef0a348 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -380,10 +380,12 @@ class TelemetryReceiverTests: XCTestCase { // MARK: - Metrics Telemetry Events func testSendTelemetryMetric() throws { + let deviceMock: DeviceInfo = .mockRandom() featureScope.contextMock = .mockWith( version: "app-version", source: "react-native", - sdkVersion: "sdk-version" + sdkVersion: "sdk-version", + device: deviceMock ) // Given @@ -408,18 +410,20 @@ class TelemetryReceiverTests: XCTestCase { DDAssertReflectionEqual(event?.telemetry.telemetryInfo[key], value) } let device = try XCTUnwrap(event?.telemetry.device) - XCTAssertTrue(device.model?.isEmpty == false) - XCTAssertTrue(device.brand?.isEmpty == false) - XCTAssertTrue(device.architecture?.isEmpty == false) + XCTAssertEqual(device.model, deviceMock.model) + XCTAssertEqual(device.brand, deviceMock.brand) + XCTAssertEqual(device.architecture, deviceMock.architecture) let os = try XCTUnwrap(event?.telemetry.os) - XCTAssertTrue(os.version?.isEmpty == false) - XCTAssertTrue(os.name?.isEmpty == false) - XCTAssertTrue(os.build?.isEmpty == false) + XCTAssertEqual(os.version, deviceMock.osVersion) + XCTAssertEqual(os.name, deviceMock.osName) + XCTAssertEqual(os.build, deviceMock.osBuildNumber) } func testSendTelemetryMetricWithRUMContext() throws { // Given let rumContext: RUMCoreContext = .mockRandom() + let deviceMock: DeviceInfo = .mockRandom() + featureScope.contextMock = .mockWith(device: deviceMock) featureScope.contextMock.baggages = [RUMFeature.name: FeatureBaggage(rumContext)] let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) @@ -433,17 +437,19 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.view?.id, rumContext.viewID) XCTAssertEqual(event?.action?.id, rumContext.userActionID) let device = try XCTUnwrap(event?.telemetry.device) - XCTAssertTrue(device.model?.isEmpty == false) - XCTAssertTrue(device.brand?.isEmpty == false) - XCTAssertTrue(device.architecture?.isEmpty == false) + XCTAssertEqual(device.model, deviceMock.model) + XCTAssertEqual(device.brand, deviceMock.brand) + XCTAssertEqual(device.architecture, deviceMock.architecture) let os = try XCTUnwrap(event?.telemetry.os) - XCTAssertTrue(os.version?.isEmpty == false) - XCTAssertTrue(os.name?.isEmpty == false) - XCTAssertTrue(os.build?.isEmpty == false) + XCTAssertEqual(os.version, deviceMock.osVersion) + XCTAssertEqual(os.name, deviceMock.osName) + XCTAssertEqual(os.build, deviceMock.osBuildNumber) } func testMethodCallTelemetryPropagetsAllData() throws { // Given + let deviceMock: DeviceInfo = .mockRandom() + featureScope.contextMock = .mockWith(device: deviceMock) let receiver = TelemetryReceiver.mockWith(featureScope: featureScope) let telemetry = TelemetryMock(with: receiver) @@ -467,13 +473,13 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.isSuccessful] as? Bool), isSuccessful) XCTAssertGreaterThan(try XCTUnwrap(event?.telemetry.telemetryInfo[MethodCalledMetric.executionTime] as? Int64), 0) let device = try XCTUnwrap(event?.telemetry.device) - XCTAssertTrue(device.model?.isEmpty == false) - XCTAssertTrue(device.brand?.isEmpty == false) - XCTAssertTrue(device.architecture?.isEmpty == false) + XCTAssertEqual(device.model, deviceMock.model) + XCTAssertEqual(device.brand, deviceMock.brand) + XCTAssertEqual(device.architecture, deviceMock.architecture) let os = try XCTUnwrap(event?.telemetry.os) - XCTAssertTrue(os.version?.isEmpty == false) - XCTAssertTrue(os.name?.isEmpty == false) - XCTAssertTrue(os.build?.isEmpty == false) + XCTAssertEqual(os.version, deviceMock.osVersion) + XCTAssertEqual(os.name, deviceMock.osName) + XCTAssertEqual(os.build, deviceMock.osBuildNumber) } func testMethodCallTelemetryDroppedWhenSampledOut() { From 220bb61ea1c45955328873ff71559f757707655f Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 12 Jun 2024 10:28:55 +0100 Subject: [PATCH 61/71] Fix complication issues on Xcode 16 --- .../Upload/BackgroundTaskCoordinator.swift | 23 +++++++++++++------ .../AppBackgroundTaskCoordinatorTests.swift | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift b/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift index 90b1adbabb..c6c417a9df 100644 --- a/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift +++ b/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift @@ -20,13 +20,22 @@ internal protocol BackgroundTaskCoordinator { import UIKit import DatadogInternal -/// Bridge protocol that matches `UIApplication` interface for background tasks. Allows easier testablity. +/// Bridge protocol that calls corresponding `UIApplication` interface for background tasks. Allows easier testablity. internal protocol UIKitAppBackgroundTaskCoordinator { - func beginBackgroundTask(expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier - func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) + func beginBgTask(_ handler: (() -> Void)?) -> UIBackgroundTaskIdentifier + func endBgTask(_ identifier: UIBackgroundTaskIdentifier) } -extension UIApplication: UIKitAppBackgroundTaskCoordinator {} +extension UIApplication: UIKitAppBackgroundTaskCoordinator { + func beginBgTask(_ handler: (() -> Void)?) -> UIBackgroundTaskIdentifier { + return beginBackgroundTask { + handler?() + } + } + func endBgTask(_ identifier: UIBackgroundTaskIdentifier) { + endBackgroundTask(identifier) + } +} internal class AppBackgroundTaskCoordinator: BackgroundTaskCoordinator { private let app: UIKitAppBackgroundTaskCoordinator? @@ -42,7 +51,7 @@ internal class AppBackgroundTaskCoordinator: BackgroundTaskCoordinator { internal func beginBackgroundTask() { endBackgroundTask() - currentTaskId = app?.beginBackgroundTask { [weak self] in + currentTaskId = app?.beginBgTask { [weak self] in guard let self = self else { return } @@ -55,13 +64,13 @@ internal class AppBackgroundTaskCoordinator: BackgroundTaskCoordinator { return } if currentTaskId != .invalid { - app?.endBackgroundTask(currentTaskId) + app?.endBgTask(currentTaskId) } self.currentTaskId = nil } } -/// Bridge protocol that matches `UIApplication` interface for background tasks. Allows easier testablity. +/// Bridge protocol that matches `ProcessInfo` interface for background activity. Allows easier testablity. internal protocol ProcessInfoActivityCoordinator { func beginActivity(options: ProcessInfo.ActivityOptions, reason: String) -> any NSObjectProtocol func endActivity(_ activity: any NSObjectProtocol) diff --git a/DatadogCore/Tests/Datadog/Core/Upload/AppBackgroundTaskCoordinatorTests.swift b/DatadogCore/Tests/Datadog/Core/Upload/AppBackgroundTaskCoordinatorTests.swift index 49d358f0ac..0c0dc11865 100644 --- a/DatadogCore/Tests/Datadog/Core/Upload/AppBackgroundTaskCoordinatorTests.swift +++ b/DatadogCore/Tests/Datadog/Core/Upload/AppBackgroundTaskCoordinatorTests.swift @@ -57,13 +57,13 @@ class AppSpy: UIKitAppBackgroundTaskCoordinator { var handler: (() -> Void)? = nil - func beginBackgroundTask(expirationHandler handler: (() -> Void)?) -> UIBackgroundTaskIdentifier { + func beginBgTask(_ handler: (() -> Void)?) -> UIBackgroundTaskIdentifier { self.handler = handler beginBackgroundTaskCalled = true return UIBackgroundTaskIdentifier(rawValue: 1) } - func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) { + func endBgTask(_ identifier: UIBackgroundTaskIdentifier) { endBackgroundTaskCalled = true } } From 5e887376499e6e1b274aa17fdfb744543cca6836 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 13:59:01 +0200 Subject: [PATCH 62/71] RUM-4829 Set `SWIFT_VERSION` through `.xcconfig` rather than overriding it in Xcode project --- Datadog/Datadog.xcodeproj/project.pbxproj | 111 ---------------------- xcconfigs/Base.xcconfig | 3 + 2 files changed, 3 insertions(+), 111 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index e196ff4573..6c60348a5f 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -9966,7 +9966,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10000,7 +9999,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10034,7 +10032,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -10064,7 +10061,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10092,7 +10088,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10120,7 +10115,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -10267,7 +10261,6 @@ PRODUCT_NAME = "$(DD_SWIFT_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10296,7 +10289,6 @@ PRODUCT_NAME = "$(DD_SWIFT_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10321,7 +10313,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/DatadogTests/DatadogTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/Example iOS"; }; @@ -10346,7 +10337,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/DatadogTests/DatadogTests-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/Example iOS"; }; @@ -10376,7 +10366,6 @@ PRODUCT_NAME = "$(DD_OBJC_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10405,7 +10394,6 @@ PRODUCT_NAME = "$(DD_OBJC_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10440,7 +10428,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10474,7 +10461,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10508,7 +10494,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -10538,7 +10523,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example iOS"; }; @@ -10567,7 +10551,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example iOS"; }; @@ -10596,7 +10579,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Example iOS"; }; @@ -10624,7 +10606,6 @@ SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/Example/Example-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_WORKSPACE = YES; }; @@ -10651,7 +10632,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/Example/Example-Bridging-Header.h"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_WORKSPACE = YES; }; @@ -10678,7 +10658,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/Example/Example-Bridging-Header.h"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_WORKSPACE = YES; }; @@ -10702,7 +10681,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = E2E; }; @@ -10726,7 +10704,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = E2E; }; @@ -10750,7 +10727,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = E2E; }; @@ -10776,7 +10752,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10801,7 +10776,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10826,7 +10800,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -10849,7 +10822,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/E2E.app/E2E"; }; @@ -10873,7 +10845,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/E2E.app/E2E"; }; @@ -10897,7 +10868,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/E2E.app/E2E"; }; @@ -10929,7 +10899,6 @@ SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -10959,7 +10928,6 @@ PRODUCT_NAME = "$(DD_CR_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -10989,7 +10957,6 @@ PRODUCT_NAME = "$(DD_CR_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11011,7 +10978,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -11033,7 +10999,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -11055,7 +11020,6 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11141,7 +11105,6 @@ PRODUCT_NAME = "$(DD_SWIFT_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11170,7 +11133,6 @@ PRODUCT_NAME = "$(DD_OBJC_SDK_PRODUCT_NAME)"; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11194,7 +11156,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/DatadogTests/DatadogTests-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example iOS.app/Example iOS"; }; @@ -11231,7 +11192,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -11267,7 +11227,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -11303,7 +11262,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11327,7 +11285,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -11350,7 +11307,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -11373,7 +11329,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11406,7 +11361,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -11438,7 +11392,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; }; name = Release; }; @@ -11470,7 +11423,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; }; name = Integration; }; @@ -11504,7 +11456,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -11538,7 +11489,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -11572,7 +11522,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11606,7 +11555,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -11640,7 +11588,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -11674,7 +11621,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -11697,7 +11643,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -11719,7 +11664,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -11741,7 +11685,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -11763,7 +11706,6 @@ SDKROOT = appletvos; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/Example/Example-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Debug; @@ -11784,7 +11726,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/Example/Example-Bridging-Header.h"; - SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Release; @@ -11805,7 +11746,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/Example/Example-Bridging-Header.h"; - SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Integration; @@ -11843,7 +11783,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -11881,7 +11820,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -11919,7 +11857,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -11957,7 +11894,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -11995,7 +11931,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -12033,7 +11968,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -12067,7 +12001,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -12101,7 +12034,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -12135,7 +12067,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -12158,7 +12089,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -12180,7 +12110,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -12202,7 +12131,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -12236,7 +12164,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -12270,7 +12197,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -12304,7 +12230,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -12327,7 +12252,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -12349,7 +12273,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -12371,7 +12294,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -12393,7 +12315,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -12414,7 +12335,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -12435,7 +12355,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -12469,7 +12388,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -12503,7 +12421,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -12537,7 +12454,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -12560,7 +12476,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -12582,7 +12497,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -12604,7 +12518,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -12634,7 +12547,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -12663,7 +12575,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; }; name = Release; }; @@ -12692,7 +12603,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; }; name = Integration; }; @@ -12715,7 +12625,6 @@ SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/DatadogTests/DatadogTests-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example tvOS.app/Example tvOS"; }; name = Debug; @@ -12738,7 +12647,6 @@ SDKROOT = appletvos; SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/DatadogTests/DatadogTests-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example tvOS.app/Example tvOS"; }; name = Release; @@ -12761,7 +12669,6 @@ SDKROOT = appletvos; SUPPORTS_MACCATALYST = NO; SWIFT_OBJC_BRIDGING_HEADER = "TargetSupport/DatadogTests/DatadogTests-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example tvOS.app/Example tvOS"; }; name = Integration; @@ -12791,7 +12698,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -12822,7 +12728,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -12852,7 +12757,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -12884,7 +12788,6 @@ SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -12915,7 +12818,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -12946,7 +12848,6 @@ SDKROOT = appletvos; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -12966,7 +12867,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.datadogqh.DatadogCrashReportingTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -12985,7 +12885,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.datadogqh.DatadogCrashReportingTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 5.0; }; name = Release; }; @@ -13004,7 +12903,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.datadogqh.DatadogCrashReportingTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; - SWIFT_VERSION = 5.0; }; name = Integration; }; @@ -13039,7 +12937,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -13074,7 +12971,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -13109,7 +13005,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; @@ -13138,7 +13033,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -13165,7 +13059,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -13192,7 +13085,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Integration; @@ -13222,7 +13114,6 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Debug; @@ -13250,7 +13141,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Release; @@ -13278,7 +13168,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; }; name = Integration; diff --git a/xcconfigs/Base.xcconfig b/xcconfigs/Base.xcconfig index fd544e20bc..6907ed93c2 100644 --- a/xcconfigs/Base.xcconfig +++ b/xcconfigs/Base.xcconfig @@ -12,5 +12,8 @@ IPHONEOS_DEPLOYMENT_TARGET=12.0 TVOS_DEPLOYMENT_TARGET=12.0 MACOSX_DEPLOYMENT_TARGET=12.6 +// Minimum supported Swift version +SWIFT_VERSION=5.9 + // Include internal base config (git-ignored, so excluded from Carthage build) #include? "Base.local.xcconfig" From 5ecb6a0813bc79f58873a48c0846ec8f61e96fda Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 15:45:15 +0200 Subject: [PATCH 63/71] RUM-4829 Do not override `SWIFT_VERSION` in `SRSnapshotTests` --- .../SRSnapshotTests/SRSnapshotTests.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcodeproj/project.pbxproj b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcodeproj/project.pbxproj index 711dbcdc9e..3a9d731a59 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcodeproj/project.pbxproj +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcodeproj/project.pbxproj @@ -463,7 +463,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; @@ -491,7 +490,6 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; @@ -509,7 +507,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.SRSnapshotTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SRHost.app/SRHost"; }; name = Debug; @@ -527,7 +524,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.datadoghq.SRSnapshotTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SRHost.app/SRHost"; }; name = Release; From 7b52bd65671af53ff5e9803f49e895520b2aca7e Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 15:46:05 +0200 Subject: [PATCH 64/71] RUM-4829 Upgrade `SRSnapshotTests` to iOS 17.5 and iPhone 15 --- .../InputViewControllers.swift | 35 ++++++++++--------- .../Utils/ImageComparison.swift | 6 ++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/InputViewControllers.swift b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/InputViewControllers.swift index 92fc35b308..fbd84fd75b 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/InputViewControllers.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/ViewControllers/InputViewControllers.swift @@ -77,29 +77,30 @@ public class DatePickersCompactViewController: UIViewController { /// Forces the "compact" date picker to open full calendar view in a popover. public func openCalendarPopover() { - // Here we use private Objc APIs. It works fine on iOS 15.0+ which matches the OS version used + // Here we use private Objc APIs. It works fine on iOS 17.5 which matches the OS version used // for snapshot tests, but might need updates in the future. - if #available(iOS 15.0, *) { - let label = datePicker.subviews[0].subviews[0] - let tapAction = NSSelectorFromString("_didTapTextLabel") - label.perform(tapAction) - } + // + // If this breaks on newer iOS version, leverage this gist: + // https://gist.github.com/ncreated/dedd8f8b628fbb820b0771e8355e32b9 + // to inspect private methods on `datePicker` and its subviews (`po datePicker.__methods`). Find the + // one that can trigger popover open and use it as `tapAction`. + let target = datePicker.subviews[0].subviews[0] + let tapAction = NSSelectorFromString("activateLabel") + target.perform(tapAction, with: nil) } /// Forces the "wheel" time picker to open in a popover. public func openTimePickerPopover() { - // Here we use private Objc APIs - it works fine on iOS 15.0+ which matches the OS version used + // Here we use private Objc APIs - it works fine on iOS 17.5 which matches the OS version used // for snapshot tests, but might need updates in the future. - if #available(iOS 15.0, *) { - class DummySender: NSObject { - @objc - func activeTouch() -> UITouch? { return nil } - } - - let label = datePicker.subviews[0].subviews[1] - let tapAction = NSSelectorFromString("didTapInputLabel:") - label.perform(tapAction, with: DummySender()) - } + // + // If this breaks on newer iOS version, leverage this gist: + // https://gist.github.com/ncreated/dedd8f8b628fbb820b0771e8355e32b9 + // to inspect private methods on `datePicker` and its subviews (`po datePicker.__methods`). Find the + // one that can trigger popover open and use it as `tapAction`. + let target = datePicker.subviews[0].subviews[1] + let tapAction = NSSelectorFromString("activateLabel") + target.perform(tapAction, with: nil) } } diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageComparison.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageComparison.swift index 3949dce7db..95d098102f 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageComparison.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/Utils/ImageComparison.swift @@ -39,7 +39,7 @@ internal extension XCTestCase { file: StaticString = #filePath, line: UInt = #line ) { - DDAssertSimulatorDevice("iPhone14,7", "16.2", file: file, line: line) + DDAssertSimulatorDevice("iPhone15,4", "iPhone15", "17.5", file: file, line: line) if record { DDSaveSnapshotIfDifferent(image: newImage, into: snapshotLocation, file: file, line: line) @@ -54,8 +54,8 @@ internal extension XCTestCase { } /// Asserts that tests are executed on given iOS Simulator. - private func DDAssertSimulatorDevice(_ expectedModel: String, _ expectedOSVersion: String, file: StaticString = #filePath, line: UInt = #line) { - _DDEvaluateAssertion(message: "Snapshots must be compared on \(expectedModel) Simulator with iOS \(expectedModel)", file: file, line: line) { + private func DDAssertSimulatorDevice(_ expectedModel: String, _ expectedModelPrettyName: String, _ expectedOSVersion: String, file: StaticString = #filePath, line: UInt = #line) { + _DDEvaluateAssertion(message: "Snapshots must be compared on \(expectedModel) Simulator (\(expectedModelPrettyName)) and iOS \(expectedOSVersion)", file: file, line: line) { guard let actualModel = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] else { throw DDAssertError.expectedFailure("Not running in Simulator") } From b1a968cf20e01f255352d2d03a818e52b583d430 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 15:50:21 +0200 Subject: [PATCH 65/71] RUM-4829 Re-generate SR snapshots for iOS 17.5 using iPhone 15 Simulator --- .../_snapshots_/pointers/testActivity().png.json | 2 +- .../_snapshots_/pointers/testAlert()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testAlert()-mask-privacy.png.json | 2 +- .../pointers/testAlert()-maskUserInput-privacy.png.json | 2 +- .../_snapshots_/pointers/testBasicShapes().png.json | 2 +- .../pointers/testBasicTexts()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testBasicTexts()-mask-privacy.png.json | 2 +- .../pointers/testBasicTexts()-maskUserInput-privacy.png.json | 2 +- .../pointers/testDatePickers()-compact-allow-privacy.png.json | 2 +- .../pointers/testDatePickers()-compact-mask-privacy.png.json | 2 +- .../testDatePickers()-compact-maskUserInput-privacy.png.json | 2 +- .../pointers/testDatePickers()-inline-allow-privacy.png.json | 2 +- .../pointers/testDatePickers()-inline-mask-privacy.png.json | 2 +- .../testDatePickers()-inline-maskUserInput-privacy.png.json | 2 +- .../pointers/testDatePickers()-wheels-allow-privacy.png.json | 2 +- .../pointers/testDatePickers()-wheels-mask-privacy.png.json | 2 +- .../testDatePickers()-wheels-maskUserInput-privacy.png.json | 2 +- .../_snapshots_/pointers/testImages()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testImages()-mask-privacy.png.json | 2 +- ...ddednavigationbarblack+nontranslucent-allow-privacy.png.json | 2 +- ...eddednavigationbarblack+nontranslucent-mask-privacy.png.json | 2 +- ...mbeddednavigationbarblack+translucent-allow-privacy.png.json | 2 +- ...embeddednavigationbarblack+translucent-mask-privacy.png.json | 2 +- ...efault+nontranslucent+backgroundcolor-allow-privacy.png.json | 2 +- ...default+nontranslucent+backgroundcolor-mask-privacy.png.json | 2 +- ...tionbardefault+nontranslucent+bartint-allow-privacy.png.json | 2 +- ...ationbardefault+nontranslucent+bartint-mask-privacy.png.json | 2 +- ...ednavigationbardefault+nontranslucent-allow-privacy.png.json | 2 +- ...dednavigationbardefault+nontranslucent-mask-privacy.png.json | 2 +- ...ardefault+translucent+backgroundcolor-allow-privacy.png.json | 2 +- ...bardefault+translucent+backgroundcolor-mask-privacy.png.json | 2 +- ...igationbardefault+translucent+bartint-allow-privacy.png.json | 2 +- ...vigationbardefault+translucent+bartint-mask-privacy.png.json | 2 +- ...eddednavigationbardefault+translucent-allow-privacy.png.json | 2 +- ...beddednavigationbardefault+translucent-mask-privacy.png.json | 2 +- .../testNavigationBars()-navigationbars-allow-privacy.png.json | 2 +- .../testNavigationBars()-navigationbars-mask-privacy.png.json | 2 +- .../_snapshots_/pointers/testPickers()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testPickers()-mask-privacy.png.json | 2 +- .../pointers/testPickers()-maskUserInput-privacy.png.json | 2 +- .../SRSnapshotTests/_snapshots_/pointers/testSafari().png.json | 2 +- .../_snapshots_/pointers/testSegments()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testSegments()-mask-privacy.png.json | 2 +- .../pointers/testSegments()-maskUserInput-privacy.png.json | 2 +- .../_snapshots_/pointers/testSliders()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testSliders()-mask-privacy.png.json | 2 +- .../pointers/testSliders()-maskUserInput-privacy.png.json | 2 +- .../_snapshots_/pointers/testSteppers()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testSteppers()-mask-privacy.png.json | 2 +- .../pointers/testSteppers()-maskUserInput-privacy.png.json | 2 +- .../SRSnapshotTests/_snapshots_/pointers/testSwiftUI().png.json | 2 +- .../_snapshots_/pointers/testSwitches()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testSwitches()-mask-privacy.png.json | 2 +- .../pointers/testSwitches()-maskUserInput-privacy.png.json | 2 +- .../_snapshots_/pointers/testTabBars()-allow-privacy.png.json | 2 +- .../testTabBars()-embeddedtabbar-allow-privacy.png.json | 2 +- .../pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json | 2 +- ...s()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json | 2 +- ...rs()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json | 2 +- .../_snapshots_/pointers/testTabBars()-mask-privacy.png.json | 2 +- .../pointers/testTextFields()-allow-privacy.png.json | 2 +- .../_snapshots_/pointers/testTextFields()-mask-privacy.png.json | 2 +- .../pointers/testTextFields()-maskUserInput-privacy.png.json | 2 +- .../pointers/testTimePickers()-compact-allow-privacy.png.json | 2 +- .../pointers/testTimePickers()-compact-mask-privacy.png.json | 2 +- .../testTimePickers()-compact-maskUserInput-privacy.png.json | 2 +- .../testTimePickers()-count-down-allow-privacy.png.json | 2 +- .../pointers/testTimePickers()-count-down-mask-privacy.png.json | 2 +- .../testTimePickers()-count-down-maskUserInput-privacy.png.json | 2 +- .../pointers/testTimePickers()-wheels-allow-privacy.png.json | 2 +- .../pointers/testTimePickers()-wheels-mask-privacy.png.json | 2 +- .../testTimePickers()-wheels-maskUserInput-privacy.png.json | 2 +- .../pointers/testUnsupportedView()-allowAll-privacy.png.json | 2 +- 73 files changed, 73 insertions(+), 73 deletions(-) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testActivity().png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testActivity().png.json index d2c49767f9..0dc59f3691 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testActivity().png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testActivity().png.json @@ -1 +1 @@ -{"hash":"b55bbf56b51224bf1074fbc5e40909deef49b1f0"} \ No newline at end of file +{"hash":"97b24b0ef97ce731761e5059cfe47c1e27d37b04"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-allow-privacy.png.json index e05ae69e2e..296e3133b6 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"55d86ba9093b978e6b730c933720d3659ad73153"} \ No newline at end of file +{"hash":"8b2d6f6a6c1a5d9bf2ad3acf4a3c3aab05a3ea5f"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-mask-privacy.png.json index e9080c1d4d..72c2eef456 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"3e6399a8242251d486b68064dcec047e998f8743"} \ No newline at end of file +{"hash":"b40d110c413ce18a4a36351c8944d8710f2bb2a8"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-maskUserInput-privacy.png.json index e05ae69e2e..296e3133b6 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testAlert()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"55d86ba9093b978e6b730c933720d3659ad73153"} \ No newline at end of file +{"hash":"8b2d6f6a6c1a5d9bf2ad3acf4a3c3aab05a3ea5f"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicShapes().png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicShapes().png.json index 138da7c49f..b35f737cef 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicShapes().png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicShapes().png.json @@ -1 +1 @@ -{"hash":"2b60e6e79414c1409812bb061a02833db90d90a1"} \ No newline at end of file +{"hash":"a319355bc86680b04a87e57b66a7e3988ddd61e8"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-allow-privacy.png.json index 973720300a..a66be52d33 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"5122a2cbc059f35ff316af5305f851b9fd91b61b"} \ No newline at end of file +{"hash":"2178a198f86cfe64d4b7f9be90918a489a4b3d56"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-mask-privacy.png.json index 6ca18ff0f7..53180e8dd1 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"9a19a17cb59679baf035e29dfa133199c4556973"} \ No newline at end of file +{"hash":"b230c301c41d0ac009396b7daac7fe99bf95f9ee"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-maskUserInput-privacy.png.json index 8e3bb0c3f0..46d9ed1575 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testBasicTexts()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"0b453d9030303123d40d02287fd0d59f87be385f"} \ No newline at end of file +{"hash":"5d2a6e27e44e5d5a82bd64a8f64ca11fe2f9d778"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-allow-privacy.png.json index 18cb143205..327d686849 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"90f61e85740ff12664140449bc71f0e82bab5614"} \ No newline at end of file +{"hash":"93048b666a6fabbbddcdd9f092214ed707e37dbb"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-mask-privacy.png.json index bb70a7e5cb..23dbbeeb17 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"f552da8eb36487b2b5f9bac4016eb4dfc399c85d"} \ No newline at end of file +{"hash":"923522736124e4d61bb69eb05a5d7bdcbab90955"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-maskUserInput-privacy.png.json index 7e3de0b4cc..6cab32797b 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-compact-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"543bda46a5875155b7c4ac7da36b5a5ef1d0df7c"} \ No newline at end of file +{"hash":"a5ad389eca25b5bb5b4e3422d4c01b7aab313f65"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-allow-privacy.png.json index 3ee26188dc..36449ac70a 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"0530319a4f495cbe6c859a13a27c41f2c4ae3937"} \ No newline at end of file +{"hash":"751fc395484ae48d389bf891f6565596414b9926"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-mask-privacy.png.json index 9c47fe298a..f4837f65d0 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"475eeedbc24b52a380a80f5c1f7a989be1d04fa6"} \ No newline at end of file +{"hash":"b78d04a207f81e7ca8eae59c73b5d82259f7350b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-maskUserInput-privacy.png.json index 0846919b52..1b6701f6aa 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-inline-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"30d47c4f80d1aba5380db0623f18ab9220e23fdc"} \ No newline at end of file +{"hash":"279121eeb7248619b4af94894b70e3165605f1cf"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-allow-privacy.png.json index 0056a87eb0..1c83622764 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"25ccfffbfc3cd017441e608852903d349e9899f3"} \ No newline at end of file +{"hash":"84346afc209b472e32103ad685848497c49b7624"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-mask-privacy.png.json index ee24cb5e82..28038c871b 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"c13455f29778a31878a25405e21620550c335c59"} \ No newline at end of file +{"hash":"45d0a6fb0e69f3962bd3bac065b7f683a112e5f2"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-maskUserInput-privacy.png.json index 0056a87eb0..1c83622764 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testDatePickers()-wheels-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"25ccfffbfc3cd017441e608852903d349e9899f3"} \ No newline at end of file +{"hash":"84346afc209b472e32103ad685848497c49b7624"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json index 015c9ab456..288960b69a 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"b34ce4960d712438e9de17c80d793d2bcfc17b4c"} \ No newline at end of file +{"hash":"f3722bae1de59f237e6df310c8f9e433c25a2fbf"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json index f250aaae2a..bca5494fe7 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testImages()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"22aadbd4fc940da34f64a353b38fed482d692316"} \ No newline at end of file +{"hash":"7a54c4867cd6cf497b8542a0e66d2a63ae6786ea"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json index 84420cc956..1f2ee9db0e 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"91655f673922fa0fae6b84b3493271755294ce66"} \ No newline at end of file +{"hash":"9f35545c0b9b6c8fffe6c69ddad38a6be9fa43ab"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json index d6d9ed125c..21cf1be9ee 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+nontranslucent-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"01f4c03dafd36ce83db11cf2e1207307c2e0953b"} \ No newline at end of file +{"hash":"c2dc323e010bfa24350ae347750a85151cc8f2e4"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json index 51e6e2a2c1..4eea75fd05 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"6ab2e5849bcb2116449a733bf5577a095f27f464"} \ No newline at end of file +{"hash":"6e72049b1dbfc5eb41874460f20d645482109c20"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json index 6deecc5d83..d181471208 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbarblack+translucent-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"b84fa4421c85e75a3be0adf36f56d4acc220d84f"} \ No newline at end of file +{"hash":"7ac725d0a4bde5dc58838e62b4ee1a5898a3667a"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json index b1ae26ee6f..8f43f89edb 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"bfc1bbf09330ed51e577daf4c54f7d084f3a7548"} \ No newline at end of file +{"hash":"2cb19056494a3fe5fbeff27e460450c910c1cf9e"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json index aa6effe3e0..eebc2ab22e 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+backgroundcolor-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"9353558262f7c7d5483f86fbf8357476047c09b4"} \ No newline at end of file +{"hash":"d3b0524ce85d1c9a225d97ed1f676c42084fb1dd"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json index 465d4e71ac..3937a1a330 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"54346586196e5c3a1add7d5ab45e0dd5709b4341"} \ No newline at end of file +{"hash":"33cc72584de4f598e4310abe1ee181b806a16dda"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json index 6529fc4c7a..bdb4bda166 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent+bartint-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"c8900122256dac31df15886a19ecf7143e6904de"} \ No newline at end of file +{"hash":"333778d3cd4ebce00f0f938efd55279fa60f2079"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json index b1c1a62f5f..f1b233781c 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"9be3b87cd188d1cd3fb73076adf1d643aac13a0c"} \ No newline at end of file +{"hash":"3d96eaed52451a24384aaee234289963efe4a883"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json index f67060ae4b..7dc1ea61a7 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+nontranslucent-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"b0b644feb2c3de1ce6fbe5c1fcc52cb2b0fb76ec"} \ No newline at end of file +{"hash":"8554b60ef18671961778854a94d7c2bc5234cca3"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json index c3fba1cf78..3cc812bd24 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"153fa612844cf744a7dd4b649927457671ad7c6b"} \ No newline at end of file +{"hash":"305ea72ce727d47de5ef75c4fc74b2f1d81a2069"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json index bdc076a01c..cb9af2a7e7 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+backgroundcolor-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"4ddbec75cbbb65a24615ebbeed7f2f84b22c1ab5"} \ No newline at end of file +{"hash":"d4e23cd38da23bde9c6078c030df68dbe5507d8d"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json index 2d8bb579c7..5dcc8b66ea 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"f128edd2df5ee16f3d6ab267b70c56b0a0aecbac"} \ No newline at end of file +{"hash":"94469c051f157a82dab0cff97fb882471597ff54"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json index 6ffbde8ce5..664faadbfe 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent+bartint-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"f01f685e3c62d1f7e70343cb17d5d75456d6c6b5"} \ No newline at end of file +{"hash":"919f66ad9de704647ec79acad25a4180401de0dc"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json index fc32693f45..e919527729 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"3963e0881cdada333af7169e14d845ab6c0de6c7"} \ No newline at end of file +{"hash":"7990169fe4acd4e31ffcdced51f90b8505b0c47e"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json index 6503e0e1e1..59e011a4bb 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-embeddednavigationbardefault+translucent-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"6572e6ab36ad61a0df026868a022223b5b23a51b"} \ No newline at end of file +{"hash":"c318765bd30ba063e77aa7e575306c362c156745"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json index 03c7e671c2..9280dc2825 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"7a636c7232e031151972b60a635fc028584be59e"} \ No newline at end of file +{"hash":"366f8a54e508538eca9317edaa34ca84dbc52c23"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json index aae8c069fe..c3ef7ea832 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testNavigationBars()-navigationbars-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"3919dcc283427b76c84faa993719f6a9533a5f09"} \ No newline at end of file +{"hash":"964d944d1c57f93f1eebc6bd7322ae9b45a5ec86"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-allow-privacy.png.json index 5737b5163f..604a232493 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"60423d7b11ffdc239cc0fe8f05f2b30e6a93a36f"} \ No newline at end of file +{"hash":"42a17d7347036a956009c93be5027c3a36212fe6"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-mask-privacy.png.json index 782f8259e9..14c5357a16 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"20a4e29d01f7db9b0649a0f59112ae5f1dd08cc2"} \ No newline at end of file +{"hash":"d33d4545ed75b76803bed3c7631cd92c385d9e19"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-maskUserInput-privacy.png.json index 3db48c786e..761e4298b7 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testPickers()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"c3413f22c223846b63f882a1cfc0fade9885e047"} \ No newline at end of file +{"hash":"5e91c622dcdd41e1305d6d71f24d137acb29bd56"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json index e9f111485d..67a6827944 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSafari().png.json @@ -1 +1 @@ -{"hash":"f9fc829946664037f512472627bd71d2328f439c"} \ No newline at end of file +{"hash":"ca707d8621657ef440640f38eaa32a3de179af45"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-allow-privacy.png.json index cb04577842..2b7492600e 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"4302021012b2b5ea0560ad33235227928bd275f1"} \ No newline at end of file +{"hash":"7dcf690a2ea20282212bd14a25a98d527becf8b1"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-mask-privacy.png.json index 8d76a90328..958d5c7363 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"fd36110a5b321e2b227aab4410a37d22a59f45cb"} \ No newline at end of file +{"hash":"0a52d46e51ebf9ec08494204a783336053627b1d"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-maskUserInput-privacy.png.json index 40068b7146..817914f083 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSegments()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"93fee15a88452ffb7093a77d977d2d1c156ccdad"} \ No newline at end of file +{"hash":"165f96dd5c00379d111e65a25ef8441dd7b15c56"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-allow-privacy.png.json index 8701a94772..d4a71c9ad2 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"2c0dc98784c7541ba4a90714a3591deccea4dd0d"} \ No newline at end of file +{"hash":"b910bb50d403bcbb00aafe859179193d2f9384ac"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-mask-privacy.png.json index 3ea42947ff..3c3d2339be 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"4657f6441bd51239197c340b0c3d688f80551558"} \ No newline at end of file +{"hash":"41d9ac3f178b8cc36ba432ac195e32624d845209"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-maskUserInput-privacy.png.json index 8f02732f0f..92ae88d36f 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSliders()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"eb11840544688b30499ace7a517b619706fd0711"} \ No newline at end of file +{"hash":"9438cf92ee621056a385019d39c63672966ad5b5"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-allow-privacy.png.json index 6874c91a32..02f29cf708 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"754deeff3786b4eb0e25309ba44f094e97ce39a0"} \ No newline at end of file +{"hash":"daf9c3dfdebc91d6ee1a62c13b108e118c25ad89"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-mask-privacy.png.json index 4464b40a27..340111295d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"95b1d4f2cfcf31b02d6bf5175b50529f8b7306fd"} \ No newline at end of file +{"hash":"22be5898bf447e60d71a23b3e1279bf5a625c77b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-maskUserInput-privacy.png.json index 42fc5abd33..0dd47da3d1 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSteppers()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"4b89935f428992aebf6dc781d1d85931155bdd6d"} \ No newline at end of file +{"hash":"90f9640d3d69a272fefb9cf985492dcbe23941a9"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwiftUI().png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwiftUI().png.json index 349441dd6a..10314d20fa 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwiftUI().png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwiftUI().png.json @@ -1 +1 @@ -{"hash":"545a8cc84c43fb1ff86cb1ad876f04c057b7a90f"} \ No newline at end of file +{"hash":"165b18361db355f16f0a740fe530986300c3e4ff"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-allow-privacy.png.json index ab7b593237..1a48329667 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"ccbf5cea54adacb4bf6cde330db4e3191878c900"} \ No newline at end of file +{"hash":"0bcee2c58273288eee5d5ad1304b835688fd7bd5"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-mask-privacy.png.json index c4869696f9..1e3b4688b8 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"c48f1928e4cc3e952a1ce5851110078414a0d45e"} \ No newline at end of file +{"hash":"27d548e32bc068f701caf3c5ea78770abaa7e68b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-maskUserInput-privacy.png.json index 80eb6e25cf..c0353b7cd4 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testSwitches()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"8a555b060d09c2f96d6da08381356b7804ba7456"} \ No newline at end of file +{"hash":"ce2f802e60a052fdff644637f8d2fa308fed4a9f"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json index 67f186dd17..aa73579e1b 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"c8db9a89ac040c3733ec239bf92a2bfd6253dee4"} \ No newline at end of file +{"hash":"dea5628a7b223b82fcd7657a3b3baa002ac94b55"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json index ee875c9a23..b8717a222d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"72d50780fb4df2e058dad0e8953e439097fd3eaa"} \ No newline at end of file +{"hash":"2824aae9011f3840aaff17eafc0255278c0fa510"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json index 14834eb0ce..a8c2288e7d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbar-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"a15b968196c3fd0a268bd3e82a37fef10b31009f"} \ No newline at end of file +{"hash":"199a3f4b6c042a007e541fc0cbc6d603b7960d37"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json index 7705782f2e..ccd7b8d6f4 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"a0cf5709cf5cb508728fd1d2ad74f6afd2153a27"} \ No newline at end of file +{"hash":"a422a17789f21f52612bf86f8753aea330a865c0"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json index 50a1a552f5..94fa3dc04a 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-embeddedtabbarunselectedtintcolor-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"dccf7efc3a8379f8cf0d1ee4d63e00a30206bc04"} \ No newline at end of file +{"hash":"1bc1eabd753b248991d26f471ebd69b13ad599cf"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json index 5028df5782..dd79383ae6 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTabBars()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"ace6b7b1f9db81e24d7d19d3fa3652e7b6f290dc"} \ No newline at end of file +{"hash":"919fe524658055240b988869fcb5c6196ae719ff"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-allow-privacy.png.json index 544c82cce0..1dac9b0f39 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"501ad05d1b1dd7410373bb1ae41af4dd77399aff"} \ No newline at end of file +{"hash":"8f34b8b8f2edb9a0e7088047b314e7af7a7d662c"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-mask-privacy.png.json index 1d3c82b1b1..59da6fa940 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"ce9e5585b468e504817081bcdeee5e0be116d7bc"} \ No newline at end of file +{"hash":"0017a4c77f9e7037b0d5a0904e6d0c61cea99cbc"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-maskUserInput-privacy.png.json index 88ddc93650..2198a5c57b 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTextFields()-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"ee2758020889cada70aa7e7c774dd0e7b3920e15"} \ No newline at end of file +{"hash":"51f714df5d592d4ac65bb89900f16576391a5ea3"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-allow-privacy.png.json index ea3132dc46..9306568005 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"2ae8d57b15c6d5c2e05b792ea8dfd13133d5eb82"} \ No newline at end of file +{"hash":"1558efaebef539c8c9dbdc6bd172925e4b856b1b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-mask-privacy.png.json index be9c4b9a46..758e4e8571 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"feaaf5058d43fa60034dac0c36f2c692c0657657"} \ No newline at end of file +{"hash":"4cab75ae802b0b0a25b9c77a8bfb2ab3ad972b2c"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-maskUserInput-privacy.png.json index ea3132dc46..9306568005 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-compact-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"2ae8d57b15c6d5c2e05b792ea8dfd13133d5eb82"} \ No newline at end of file +{"hash":"1558efaebef539c8c9dbdc6bd172925e4b856b1b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-allow-privacy.png.json index bbc6f3e1e2..4ecd50291d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"9702b8ec011f7f7016143ba3f5e9b471303a7e5c"} \ No newline at end of file +{"hash":"f8aa63a3da2cf255800713d2dafbd8771804906b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-mask-privacy.png.json index 119393ea8f..1904ce62f2 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"59c0a6e550ad54752688697e32e616fce1696f96"} \ No newline at end of file +{"hash":"9529fd7fb9bd5c3a9c6cf48770871e13546ac74a"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-maskUserInput-privacy.png.json index bbc6f3e1e2..4ecd50291d 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-count-down-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"9702b8ec011f7f7016143ba3f5e9b471303a7e5c"} \ No newline at end of file +{"hash":"f8aa63a3da2cf255800713d2dafbd8771804906b"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-allow-privacy.png.json index bf8adb9745..e4c1021c57 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-allow-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-allow-privacy.png.json @@ -1 +1 @@ -{"hash":"8f5072f5768474039d13510864cbf280b282ae52"} \ No newline at end of file +{"hash":"4d4470b93a19343eb8c3f98b26cedd2dbfb080f8"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-mask-privacy.png.json index 191e460f47..3ad2462878 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-mask-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-mask-privacy.png.json @@ -1 +1 @@ -{"hash":"c45d507cac6826dcabe48a211bbcf685b5cadfd5"} \ No newline at end of file +{"hash":"3bccfa97a3fc2b32ccd9666749b02e103c74e6fd"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-maskUserInput-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-maskUserInput-privacy.png.json index bf8adb9745..e4c1021c57 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-maskUserInput-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testTimePickers()-wheels-maskUserInput-privacy.png.json @@ -1 +1 @@ -{"hash":"8f5072f5768474039d13510864cbf280b282ae52"} \ No newline at end of file +{"hash":"4d4470b93a19343eb8c3f98b26cedd2dbfb080f8"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testUnsupportedView()-allowAll-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testUnsupportedView()-allowAll-privacy.png.json index 6ccaf04733..a836e8af78 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testUnsupportedView()-allowAll-privacy.png.json +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testUnsupportedView()-allowAll-privacy.png.json @@ -1 +1 @@ -{"hash":"8c803bf8fe88005af09b838159af0d517170090b"} \ No newline at end of file +{"hash":"a58d46d583ae2cd4ddf8024d74dc35ed095a707d"} \ No newline at end of file From 86c20da99d8b25da31a21a20a99aebbc178d2344 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 15:53:51 +0200 Subject: [PATCH 66/71] RUM-4829 Switch to iOS 17.5 and iPhone 15 for running SR snapshot tests on CI --- bitrise.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitrise.yml b/bitrise.yml index 2e5e09f36f..c9c1872cbb 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -278,7 +278,7 @@ workflows: run_if: '{{enveq "DD_RUN_SR_UNIT_TESTS" "1"}}' inputs: - scheme: SRSnapshotTests - - destination: platform=iOS Simulator,name=iPhone 14,OS=16.2 + - destination: platform=iOS Simulator,name=iPhone 15,OS=17.5 - should_build_before_test: 'no' - is_clean_build: 'no' - generate_code_coverage_files: 'yes' From 4ab86be38d7a6a18a5bbe035f98d3b53550244ca Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 16:14:48 +0200 Subject: [PATCH 67/71] RUM-4829 Upgrade Swift version in `Package.swift` and `podspecs` --- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogInternal.podspec | 2 +- DatadogLogs.podspec | 2 +- DatadogObjc.podspec | 2 +- DatadogRUM.podspec | 2 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- DatadogTrace.podspec | 2 +- DatadogWebViewTracking.podspec | 2 +- Package.swift | 2 +- TestUtilities.podspec | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index 22754e5e0f..e31ff7c139 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogCore.podspec b/DatadogCore.podspec index 508a83fff7..06cd31ac07 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index 7035861afd..1be409c7b7 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index ced6b3a5da..d599d2546e 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index e17d1d036e..e6313dadd4 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index 8b7aec3131..5697a02c37 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index 7dd1267549..b13f6ddbfe 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index 12fd9aed2e..502e78bc16 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index b1687aa013..9bb6ee4532 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -17,7 +17,7 @@ Pod::Spec.new do |s| s.deprecated_in_favor_of = "DatadogAlamofireExtension" - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index 9e212137c9..2bd6cbd8b8 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| "Maciej Burda" => "maciej.burda@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 2ef9406987..bc00762521 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| "Ganesh Jangir" => "ganesh.jangir@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index 833b120d14..bf8ef9a617 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Ganesh Jangir" => "ganesh.jangir@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 5a3c91bccd..3621483195 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Ganesh Jangir" => "ganesh.jangir@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' diff --git a/DatadogWebViewTracking.podspec b/DatadogWebViewTracking.podspec index e5a8843855..acd918301f 100644 --- a/DatadogWebViewTracking.podspec +++ b/DatadogWebViewTracking.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Ganesh Jangir" => "ganesh.jangir@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/Package.swift b/Package.swift index 4411ae8d09..e62f4f19d7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7.1 +// swift-tools-version: 5.9 import PackageDescription import Foundation diff --git a/TestUtilities.podspec b/TestUtilities.podspec index 7c4a390ee4..a304424522 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |s| "Ganesh Jangir" => "ganesh.jangir@datadoghq.com" } - s.swift_version = '5.7.1' + s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' From 54edf6deeb4b4f19e2cb6fa3c78d96186e71e941 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Wed, 12 Jun 2024 16:23:04 +0200 Subject: [PATCH 68/71] RUM-4829 Set `SWIFT_VERSION` through `Base.xcconfig` for `IntegrationTests` --- IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj | 6 ------ IntegrationTests/xcconfigs/Base.xcconfig | 3 +++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj index 37dd4ac5f6..58125a0ff9 100644 --- a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj +++ b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj @@ -1314,7 +1314,6 @@ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; - SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Debug; @@ -1336,7 +1335,6 @@ PRODUCT_NAME = "Integration Tests Runner"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; - SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Release; @@ -1358,7 +1356,6 @@ PRODUCT_NAME = "Integration Tests Runner"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_TREAT_WARNINGS_AS_ERRORS = NO; - SWIFT_VERSION = 5.0; VALIDATE_WORKSPACE = YES; }; name = Integration; @@ -1378,7 +1375,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG DD_COMPILED_FOR_INTEGRATION_TESTS"; - SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "Runner iOS"; VALIDATE_WORKSPACE = YES; }; @@ -1399,7 +1395,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DD_COMPILED_FOR_INTEGRATION_TESTS; - SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "Runner iOS"; VALIDATE_WORKSPACE = YES; }; @@ -1420,7 +1415,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DD_COMPILED_FOR_INTEGRATION_TESTS; - SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "Runner iOS"; VALIDATE_WORKSPACE = YES; }; diff --git a/IntegrationTests/xcconfigs/Base.xcconfig b/IntegrationTests/xcconfigs/Base.xcconfig index d0d1d5422a..c7874ff440 100644 --- a/IntegrationTests/xcconfigs/Base.xcconfig +++ b/IntegrationTests/xcconfigs/Base.xcconfig @@ -2,6 +2,9 @@ IPHONEOS_DEPLOYMENT_TARGET=12.0 TVOS_DEPLOYMENT_TARGET=12.0 +// Minimum supported Swift version +SWIFT_VERSION=5.9 + // Add common settings from Datadog.xcconfig #include "../xcconfigs/Datadog.xcconfig" From 9e9c88de071e87e9b385861b300339df71ff3ffd Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 12 Jun 2024 14:19:02 +0100 Subject: [PATCH 69/71] Bumped version to 2.13.0 --- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 2 +- DatadogCore/Sources/Versioning.swift | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogInternal.podspec | 2 +- DatadogLogs.podspec | 2 +- DatadogObjc.podspec | 2 +- DatadogRUM.podspec | 2 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- DatadogTrace.podspec | 2 +- DatadogWebViewTracking.podspec | 2 +- TestUtilities.podspec | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index e31ff7c139..eda30456ee 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogAlamofireExtension" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore.podspec b/DatadogCore.podspec index 06cd31ac07..36bdcc9523 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCore" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore/Sources/Versioning.swift b/DatadogCore/Sources/Versioning.swift index faeed4bc02..238c6822ab 100644 --- a/DatadogCore/Sources/Versioning.swift +++ b/DatadogCore/Sources/Versioning.swift @@ -1,3 +1,3 @@ // GENERATED FILE: Do not edit directly -internal let __sdkVersion = "2.12.0" +internal let __sdkVersion = "2.13.0" diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index 1be409c7b7..95c9775171 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCrashReporting" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index d599d2546e..46048a1330 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogInternal" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Datadog Internal Package. This module is not for public use." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index e6313dadd4..110ed51c2a 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogLogs" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Datadog Logs Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index 5697a02c37..1899de9aa7 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogObjc" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index b13f6ddbfe..ae25d3ddcf 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogRUM" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Datadog Real User Monitoring Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index 502e78bc16..1d26951b26 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSDK" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index 9bb6ee4532..e79013aac6 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKAlamofireExtension" s.module_name = "DatadogAlamofireExtension" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index 2bd6cbd8b8..fd38b0dbc1 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKCrashReporting" s.module_name = "DatadogCrashReporting" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index bc00762521..5a4d1b83bd 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKObjc" s.module_name = "DatadogObjc" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index bf8ef9a617..cbb9450b3c 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSessionReplay" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Official Datadog Session Replay SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 3621483195..213d6f9e20 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogTrace" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Datadog Trace Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogWebViewTracking.podspec b/DatadogWebViewTracking.podspec index acd918301f..37771b84ac 100644 --- a/DatadogWebViewTracking.podspec +++ b/DatadogWebViewTracking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogWebViewTracking" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Datadog WebView Tracking Module." s.homepage = "https://www.datadoghq.com" diff --git a/TestUtilities.podspec b/TestUtilities.podspec index a304424522..cb149285cb 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TestUtilities" - s.version = "2.12.0" + s.version = "2.13.0" s.summary = "Datadog Testing Utilities. This module is for internal testing and should not be published." s.homepage = "https://www.datadoghq.com" From 3f89414306f7dd32a75bfef1542e5dca540fcc71 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 12 Jun 2024 14:21:31 +0100 Subject: [PATCH 70/71] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f58a74253..00ee022123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Unreleased +# 2.13.0 / 12-06-2024 + - [IMPROVEMENT] Bump `IPHONEOS_DEPLOYMENT_TARGET` and `TVOS_DEPLOYMENT_TARGET` from 11 to 12. See [#1891][] - [IMPROVEMENT] Add `.connect`, `.trace`, `.options` values to `DDRUMMethod` type. See [#1886][] +- [FIX] Fix compilation issues on Xcode 16 beta. See [#1898][] # 2.12.0 / 03-06-2024 @@ -680,6 +683,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1828]: https://github.com/DataDog/dd-sdk-ios/pull/1828 [#1835]: https://github.com/DataDog/dd-sdk-ios/pull/1835 [#1886]: https://github.com/DataDog/dd-sdk-ios/pull/1886 +[#1898]: https://github.com/DataDog/dd-sdk-ios/pull/1898 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu From 602f8e8a7724b483c44a5fa96f271b442c617748 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Thu, 13 Jun 2024 10:39:33 +0100 Subject: [PATCH 71/71] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00ee022123..283ebe244d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -# 2.13.0 / 12-06-2024 +# 2.13.0 / 13-06-2024 - [IMPROVEMENT] Bump `IPHONEOS_DEPLOYMENT_TARGET` and `TVOS_DEPLOYMENT_TARGET` from 11 to 12. See [#1891][] - [IMPROVEMENT] Add `.connect`, `.trace`, `.options` values to `DDRUMMethod` type. See [#1886][]