diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 94109968fa..5828e2b405 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -850,12 +850,18 @@ 86092FDE2DEDC6830075D63B /* AccessibilityValuesMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86092FDC2DEDC6830075D63B /* AccessibilityValuesMock.swift */; }; 862B0B272DEF2D8000F61E73 /* MockNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 862B0B262DEF2D8000F61E73 /* MockNotificationCenter.swift */; }; 862B0B282DEF2D8000F61E73 /* MockNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 862B0B262DEF2D8000F61E73 /* MockNotificationCenter.swift */; }; + 8648D0782E01A389002F226B /* BrightnessLevelPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8648D0772E01A389002F226B /* BrightnessLevelPublisher.swift */; }; + 8648D0792E01A389002F226B /* BrightnessLevelPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8648D0772E01A389002F226B /* BrightnessLevelPublisher.swift */; }; + 8648D07B2E01ABB0002F226B /* BrightnessLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8648D07A2E01ABB0002F226B /* BrightnessLevel.swift */; }; + 8648D07C2E01ABB0002F226B /* BrightnessLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8648D07A2E01ABB0002F226B /* BrightnessLevel.swift */; }; 864A70792DDF742A00AC0619 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864A70782DDF742A00AC0619 /* Accessibility.swift */; }; 864A707A2DDF742A00AC0619 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864A70782DDF742A00AC0619 /* Accessibility.swift */; }; 864A707C2DDF743900AC0619 /* AccessibilityReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864A707B2DDF743900AC0619 /* AccessibilityReader.swift */; }; 864A707D2DDF743900AC0619 /* AccessibilityReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864A707B2DDF743900AC0619 /* AccessibilityReader.swift */; }; 864A70802DE092AD00AC0619 /* AccessibilityReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864A707E2DE092AD00AC0619 /* AccessibilityReaderTests.swift */; }; 864A70812DE092AD00AC0619 /* AccessibilityReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 864A707E2DE092AD00AC0619 /* AccessibilityReaderTests.swift */; }; + 866CA4A82E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866CA4A72E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift */; }; + 866CA4A92E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866CA4A72E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift */; }; 960A0D3B2D6E2490004BB999 /* Reflector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960A0D3A2D6E2490004BB999 /* Reflector.swift */; }; 960A0D3C2D6E2490004BB999 /* CustomDump.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960A0D382D6E2490004BB999 /* CustomDump.swift */; }; 960A0D3D2D6E2490004BB999 /* ReflectionMirror.swift in Sources */ = {isa = PBXBuildFile; fileRef = 960A0D392D6E2490004BB999 /* ReflectionMirror.swift */; }; @@ -2954,9 +2960,12 @@ 61FF9A4425AC5DEA001058CC /* ViewIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewIdentifier.swift; sourceTree = ""; }; 86092FDC2DEDC6830075D63B /* AccessibilityValuesMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityValuesMock.swift; sourceTree = ""; }; 862B0B262DEF2D8000F61E73 /* MockNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNotificationCenter.swift; sourceTree = ""; }; + 8648D0772E01A389002F226B /* BrightnessLevelPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessLevelPublisher.swift; sourceTree = ""; }; + 8648D07A2E01ABB0002F226B /* BrightnessLevel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessLevel.swift; sourceTree = ""; }; 864A70782DDF742A00AC0619 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; 864A707B2DDF743900AC0619 /* AccessibilityReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityReader.swift; sourceTree = ""; }; 864A707E2DE092AD00AC0619 /* AccessibilityReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityReaderTests.swift; sourceTree = ""; }; + 866CA4A72E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessLevelPublisherTests.swift; sourceTree = ""; }; 960A0D382D6E2490004BB999 /* CustomDump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDump.swift; sourceTree = ""; }; 960A0D392D6E2490004BB999 /* ReflectionMirror.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReflectionMirror.swift; sourceTree = ""; }; 960A0D3A2D6E2490004BB999 /* Reflector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reflector.swift; sourceTree = ""; }; @@ -5438,6 +5447,7 @@ D26C49AE2886DC7B00802B2D /* ApplicationStatePublisherTests.swift */, D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */, D234613028B7712F00055D4C /* FeatureContextTests.swift */, + 866CA4A72E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift */, ); path = Context; sourceTree = ""; @@ -6232,6 +6242,7 @@ D23039BE298D5235001A1FA3 /* LaunchTime.swift */, D2F8235229915E12003C7E99 /* DatadogSite.swift */, 6174D6122BFDF16C00EC7469 /* BundleType.swift */, + 8648D07A2E01ABB0002F226B /* BrightnessLevel.swift */, ); path = Context; sourceTree = ""; @@ -6793,6 +6804,7 @@ D2EFA867286DA85700F1FAA6 /* DatadogContextProvider.swift */, D20605A2287464F40047275C /* ContextValuePublisher.swift */, D20605A5287476230047275C /* ServerOffsetPublisher.swift */, + 8648D0772E01A389002F226B /* BrightnessLevelPublisher.swift */, D20605A82874C1CD0047275C /* NetworkConnectionInfoPublisher.swift */, D20605B12874E1660047275C /* CarrierInfoPublisher.swift */, D2A1EE31287DA51900D28DFB /* UserInfoPublisher.swift */, @@ -8376,6 +8388,7 @@ D26C49BF288982DA00802B2D /* FeatureUpload.swift in Sources */, A70A82652A935F210072F5DC /* BackgroundTaskCoordinator.swift in Sources */, 61D3E0D2277B23F1008BE766 /* KronosInternetAddress.swift in Sources */, + 8648D0782E01A389002F226B /* BrightnessLevelPublisher.swift in Sources */, D2553826288F0B1A00727FAD /* BatteryStatusPublisher.swift in Sources */, 61D3E0D5277B23F1008BE766 /* KronosNTPPacket.swift in Sources */, 6128F5712BA223D100D35B08 /* DataStore+TLV.swift in Sources */, @@ -8466,6 +8479,7 @@ 61133C582423990D00786299 /* FileWriterTests.swift in Sources */, D22743DC29DEB8B4001A7EF9 /* VitalRefreshRateReaderTests.swift in Sources */, 617B954224BF4E7600E6F443 /* RUMMonitorConfigurationTests.swift in Sources */, + 866CA4A92E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift in Sources */, 3C0D5DE22A543DC400446CF9 /* EventGeneratorTests.swift in Sources */, 6136CB4A2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */, 266BFA5E2D6F4E31003041A5 /* AccountInfoPublisherTests.swift in Sources */, @@ -8978,6 +8992,7 @@ D2303A01298D5236001A1FA3 /* DateFormatting.swift in Sources */, D23039F1298D5236001A1FA3 /* AnyDecodable.swift in Sources */, 6167E6E22B81207200C3CA2D /* DDCrashReport.swift in Sources */, + 8648D07B2E01ABB0002F226B /* BrightnessLevel.swift in Sources */, D2160CC529C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, 6167E6FD2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */, D23039DD298D5235001A1FA3 /* DD.swift in Sources */, @@ -9751,6 +9766,7 @@ D2612F48290197C700509B7D /* LaunchTimePublisher.swift in Sources */, A70A82662A935F210072F5DC /* BackgroundTaskCoordinator.swift in Sources */, D2A1EE24287740B500D28DFB /* ApplicationStatePublisher.swift in Sources */, + 8648D0792E01A389002F226B /* BrightnessLevelPublisher.swift in Sources */, D2CB6E2927C50EAE00A62B57 /* KronosInternetAddress.swift in Sources */, 6128F5722BA223D100D35B08 /* DataStore+TLV.swift in Sources */, D2CB6E2C27C50EAE00A62B57 /* KronosNTPPacket.swift in Sources */, @@ -9861,6 +9877,7 @@ D2CB6F1A27C520D400A62B57 /* FileReaderTests.swift in Sources */, 266BFA5F2D6F4E31003041A5 /* AccountInfoPublisherTests.swift in Sources */, D2777D9E29F6A75800FFBB40 /* TelemetryReceiverTests.swift in Sources */, + 866CA4A82E02EE0100E0CD03 /* BrightnessLevelPublisherTests.swift in Sources */, D2A1EE39287EEB7600D28DFB /* NetworkConnectionInfoPublisherTests.swift in Sources */, D2CB6F1D27C520D400A62B57 /* DataUploaderTests.swift in Sources */, D2A1EE35287EB8DB00D28DFB /* ServerOffsetPublisherTests.swift in Sources */, @@ -10083,6 +10100,7 @@ 6167E6E32B81207200C3CA2D /* DDCrashReport.swift in Sources */, D2160CC629C0DED100FAA9A5 /* URLSessionTaskInterception.swift in Sources */, 6167E6FE2B81EC0400C3CA2D /* BacktraceReporter.swift in Sources */, + 8648D07C2E01ABB0002F226B /* BrightnessLevel.swift in Sources */, D2DA2372298D57AA00C6C7E6 /* DD.swift in Sources */, D2160C9B29C0DE5700FAA9A5 /* FirstPartyHosts.swift in Sources */, D2EBEE3029BA161100B15732 /* TracePropagationHeadersReader.swift in Sources */, diff --git a/DatadogCore/Sources/Core/Context/BrightnessLevelPublisher.swift b/DatadogCore/Sources/Core/Context/BrightnessLevelPublisher.swift new file mode 100644 index 0000000000..7a31505f60 --- /dev/null +++ b/DatadogCore/Sources/Core/Context/BrightnessLevelPublisher.swift @@ -0,0 +1,55 @@ +/* + * 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 + +#if os(iOS) +import UIKit + +/// A publisher that publishes the screen brightness level from UIScreen. +internal final class BrightnessLevelPublisher: ContextValuePublisher { + /// The initial brightness level. + let initialValue: BrightnessLevel? + + /// The notification center to observe brightness changes. + private let notificationCenter: NotificationCenter + private let screen: UIScreen + private var observers: [Any]? = nil + + init(notificationCenter: NotificationCenter = .default, screen: UIScreen = .main) { + self.notificationCenter = notificationCenter + self.screen = screen + self.initialValue = Float(screen.brightness) + } + + /// Publishes the brightness level to the given receiver. + /// + /// - Parameter receiver: The receiver to publish the brightness level to. + func publish(to receiver: @escaping ContextValueReceiver) { + let block = { (notification: Notification) in + receiver(Float(self.screen.brightness)) + } + + observers = [ + notificationCenter.addObserver( + forName: UIScreen.brightnessDidChangeNotification, + object: nil, + queue: .main, + using: block + ) + ] + + receiver(initialValue) + } + + func cancel() { + observers?.forEach(notificationCenter.removeObserver) + observers = nil + } +} + +#endif diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 470622958b..07f401fa5c 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -525,6 +525,10 @@ extension DatadogContextProvider { subscribe(\.isLowPowerModeEnabled, to: LowPowerModePublisher(notificationCenter: notificationCenter, processInfo: processInfo)) #endif + #if os(iOS) + subscribe(\.brightnessLevel, to: BrightnessLevelPublisher(notificationCenter: notificationCenter)) + #endif + #if os(iOS) || os(tvOS) DispatchQueue.main.async { // must be call on the main thread to read `UIApplication.State` diff --git a/DatadogCore/Tests/Datadog/DatadogCore/Context/BrightnessLevelPublisherTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/Context/BrightnessLevelPublisherTests.swift new file mode 100644 index 0000000000..e31e31c7ae --- /dev/null +++ b/DatadogCore/Tests/Datadog/DatadogCore/Context/BrightnessLevelPublisherTests.swift @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#if os(iOS) + +import XCTest +@testable import TestUtilities +@testable import DatadogCore + +final class BrightnessLevelPublisherTests: XCTestCase { + private let notificationCenter = MockNotificationCenter() + + func testInitialValue() throws { + // Given + let publisher = BrightnessLevelPublisher(notificationCenter: notificationCenter) + + // Then + XCTAssertNotNil(publisher.initialValue) + XCTAssertEqual(publisher.initialValue, Float(UIScreen.main.brightness)) + } + + func testMultipleBrightnessChanges() throws { + let expectation1 = self.expectation(description: "first brightness change") + let expectation2 = self.expectation(description: "second brightness change") + + // Given + let mockScreen = UIScreenMock(brightness: 0.2) + let publisher = BrightnessLevelPublisher(notificationCenter: notificationCenter, screen: mockScreen) + var receivedValues: [Float] = [] + + publisher.publish { level in + if let level = level { + receivedValues.append(level) + + switch receivedValues.count { + case 1: + expectation1.fulfill() + case 2: + expectation2.fulfill() + default: + break + } + } + } + + // When + mockScreen.brightness = 0.5 + notificationCenter.postFakeNotification(name: UIScreen.brightnessDidChangeNotification) + mockScreen.brightness = 0.8 + notificationCenter.postFakeNotification(name: UIScreen.brightnessDidChangeNotification) + + wait(for: [expectation1, expectation2], timeout: 0.1) + + // Then + XCTAssertEqual(receivedValues.count, 3) + XCTAssertEqual(receivedValues[0], 0.2) + XCTAssertEqual(receivedValues[1], 0.5) + XCTAssertEqual(receivedValues[2], 0.8) + } +} + +#endif diff --git a/DatadogInternal/Sources/Context/BrightnessLevel.swift b/DatadogInternal/Sources/Context/BrightnessLevel.swift new file mode 100644 index 0000000000..adc208d753 --- /dev/null +++ b/DatadogInternal/Sources/Context/BrightnessLevel.swift @@ -0,0 +1,10 @@ +/* + * 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 + +/// Type alias for screen brightness level for mobile devices. +public typealias BrightnessLevel = Float diff --git a/DatadogInternal/Sources/Context/DatadogContext.swift b/DatadogInternal/Sources/Context/DatadogContext.swift index 023c637804..6fde231c01 100644 --- a/DatadogInternal/Sources/Context/DatadogContext.swift +++ b/DatadogInternal/Sources/Context/DatadogContext.swift @@ -107,6 +107,9 @@ public struct DatadogContext { /// This value can be `nil` of the current device battery interface is not available. public var batteryStatus: BatteryStatus? + /// The current brightness status. + public var brightnessLevel: BrightnessLevel? + /// `true` if the Low Power Mode is enabled. public var isLowPowerModeEnabled = false @@ -141,6 +144,7 @@ public struct DatadogContext { networkConnectionInfo: NetworkConnectionInfo? = nil, carrierInfo: CarrierInfo? = nil, batteryStatus: BatteryStatus? = nil, + brightnessLevel: BrightnessLevel? = nil, isLowPowerModeEnabled: Bool = false, additionalContext: [String: AdditionalContext] = [:] ) { @@ -170,6 +174,7 @@ public struct DatadogContext { self.networkConnectionInfo = networkConnectionInfo self.carrierInfo = carrierInfo self.batteryStatus = batteryStatus + self.brightnessLevel = brightnessLevel self.isLowPowerModeEnabled = isLowPowerModeEnabled self.additionalContext = additionalContext } diff --git a/DatadogInternal/Sources/Models/CrashReporting/CrashContext.swift b/DatadogInternal/Sources/Models/CrashReporting/CrashContext.swift index 9484f53d59..272cb48931 100644 --- a/DatadogInternal/Sources/Models/CrashReporting/CrashContext.swift +++ b/DatadogInternal/Sources/Models/CrashReporting/CrashContext.swift @@ -65,6 +65,17 @@ public struct CrashContext: Codable, Equatable { /// not support telephony services. public let carrierInfo: CarrierInfo? + /// The current mobile device battery status. + /// + /// This value can be `nil` of the current device battery interface is not available. + public var batteryStatus: BatteryStatus? + + /// The current brightness status. + public var brightnessLevel: BrightnessLevel? + + /// `true` if the Low Power Mode is enabled. + public var isLowPowerModeEnabled = false + /// The last _"Is app in foreground?"_ information from crashed app process. public let lastIsAppInForeground: Bool @@ -96,6 +107,9 @@ public struct CrashContext: Codable, Equatable { accountInfo: AccountInfo?, networkConnectionInfo: NetworkConnectionInfo?, carrierInfo: CarrierInfo?, + batteryStatus: BatteryStatus?, + brightnessLevel: BrightnessLevel?, + isLowPowerModeEnabled: Bool, lastIsAppInForeground: Bool, appLaunchDate: Date?, lastRUMViewEvent: RUMViewEvent?, @@ -116,6 +130,9 @@ public struct CrashContext: Codable, Equatable { self.accountInfo = accountInfo self.networkConnectionInfo = networkConnectionInfo self.carrierInfo = carrierInfo + self.batteryStatus = batteryStatus + self.brightnessLevel = brightnessLevel + self.isLowPowerModeEnabled = isLowPowerModeEnabled self.lastIsAppInForeground = lastIsAppInForeground self.appLaunchDate = appLaunchDate self.lastRUMViewEvent = lastRUMViewEvent diff --git a/DatadogInternal/Sources/Models/RUM/RUMDataModels.swift b/DatadogInternal/Sources/Models/RUM/RUMDataModels.swift index 64e09679d1..5abb93f1ca 100644 --- a/DatadogInternal/Sources/Models/RUM/RUMDataModels.swift +++ b/DatadogInternal/Sources/Models/RUM/RUMDataModels.swift @@ -331,6 +331,9 @@ public struct RUMActionEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution public struct Configuration: Codable { + /// The percentage of views profiled + public let profilingSampleRate: Double? + /// The percentage of sessions with RUM & Session Replay pricing tracked public let sessionReplaySampleRate: Double? @@ -338,6 +341,7 @@ public struct RUMActionEvent: RUMDataModel { public let sessionSampleRate: Double public enum CodingKeys: String, CodingKey { + case profilingSampleRate = "profiling_sample_rate" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" } @@ -345,12 +349,15 @@ public struct RUMActionEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution /// /// - Parameters: + /// - profilingSampleRate: The percentage of views profiled /// - sessionReplaySampleRate: The percentage of sessions with RUM & Session Replay pricing tracked /// - sessionSampleRate: The percentage of sessions tracked public init( + profilingSampleRate: Double? = nil, sessionReplaySampleRate: Double? = nil, sessionSampleRate: Double ) { + self.profilingSampleRate = profilingSampleRate self.sessionReplaySampleRate = sessionReplaySampleRate self.sessionSampleRate = sessionSampleRate } @@ -1056,6 +1063,9 @@ public struct RUMErrorEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution public struct Configuration: Codable { + /// The percentage of views profiled + public let profilingSampleRate: Double? + /// The percentage of sessions with RUM & Session Replay pricing tracked public let sessionReplaySampleRate: Double? @@ -1063,6 +1073,7 @@ public struct RUMErrorEvent: RUMDataModel { public let sessionSampleRate: Double public enum CodingKeys: String, CodingKey { + case profilingSampleRate = "profiling_sample_rate" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" } @@ -1070,12 +1081,15 @@ public struct RUMErrorEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution /// /// - Parameters: + /// - profilingSampleRate: The percentage of views profiled /// - sessionReplaySampleRate: The percentage of sessions with RUM & Session Replay pricing tracked /// - sessionSampleRate: The percentage of sessions tracked public init( + profilingSampleRate: Double? = nil, sessionReplaySampleRate: Double? = nil, sessionSampleRate: Double ) { + self.profilingSampleRate = profilingSampleRate self.sessionReplaySampleRate = sessionReplaySampleRate self.sessionSampleRate = sessionSampleRate } @@ -1468,6 +1482,7 @@ public struct RUMErrorEvent: RUMDataModel { case exception = "Exception" case watchdogTermination = "Watchdog Termination" case memoryWarning = "Memory Warning" + case network = "Network" } /// Properties for one of the error causes @@ -2115,6 +2130,9 @@ public struct RUMLongTaskEvent: RUMDataModel { /// Version of the RUM event format public let formatVersion: Int64 = 2 + /// Profiling context + public let profiling: Profiling? + /// SDK name (e.g. 'logs', 'rum', 'rum-slim', etc.) public let sdkName: String? @@ -2126,6 +2144,7 @@ public struct RUMLongTaskEvent: RUMDataModel { case configuration = "configuration" case discarded = "discarded" case formatVersion = "format_version" + case profiling = "profiling" case sdkName = "sdk_name" case session = "session" } @@ -2136,24 +2155,30 @@ public struct RUMLongTaskEvent: RUMDataModel { /// - browserSdkVersion: Browser SDK version /// - configuration: Subset of the SDK configuration options in use during its execution /// - discarded: Whether the long task should be discarded or indexed + /// - profiling: Profiling context /// - sdkName: SDK name (e.g. 'logs', 'rum', 'rum-slim', etc.) /// - session: Session-related internal properties public init( browserSdkVersion: String? = nil, configuration: Configuration? = nil, discarded: Bool? = nil, + profiling: Profiling? = nil, sdkName: String? = nil, session: Session? = nil ) { self.browserSdkVersion = browserSdkVersion self.configuration = configuration self.discarded = discarded + self.profiling = profiling self.sdkName = sdkName self.session = session } /// Subset of the SDK configuration options in use during its execution public struct Configuration: Codable { + /// The percentage of views profiled + public let profilingSampleRate: Double? + /// The percentage of sessions with RUM & Session Replay pricing tracked public let sessionReplaySampleRate: Double? @@ -2161,6 +2186,7 @@ public struct RUMLongTaskEvent: RUMDataModel { public let sessionSampleRate: Double public enum CodingKeys: String, CodingKey { + case profilingSampleRate = "profiling_sample_rate" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" } @@ -2168,17 +2194,102 @@ public struct RUMLongTaskEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution /// /// - Parameters: + /// - profilingSampleRate: The percentage of views profiled /// - sessionReplaySampleRate: The percentage of sessions with RUM & Session Replay pricing tracked /// - sessionSampleRate: The percentage of sessions tracked public init( + profilingSampleRate: Double? = nil, sessionReplaySampleRate: Double? = nil, sessionSampleRate: Double ) { + self.profilingSampleRate = profilingSampleRate self.sessionReplaySampleRate = sessionReplaySampleRate self.sessionSampleRate = sessionSampleRate } } + /// Profiling context + public struct Profiling: Codable { + /// The reason the Profiler encountered an error. This attribute is only present if the status is `error`. + /// + /// Possible values: + /// - `not-supported-by-browser`: The browser does not support the Profiler (i.e., `window.Profiler` is not available). + /// - `failed-to-lazy-load`: The Profiler script failed to be loaded by the browser (may be a connection issue or the chunk was not found). + /// - `missing-document-policy-header`: The Profiler failed to start because its missing `Document-Policy: js-profiling` HTTP response header. + /// - `unexpected-exception`: An exception occurred when starting the Profiler. + public let errorReason: ErrorReason? + + /// Used to track the status of the RUM Profiler. + /// + /// They are defined in order of when they can happen, from the moment the SDK is initialized to the moment the Profiler is actually running. + /// + /// - `starting`: The Profiler is starting (i.e., when the SDK just started). This is the initial status. + /// - `running`: The Profiler is running. + /// - `stopped`: The Profiler is stopped. + /// - `error`: The Profiler encountered an error. See `error_reason` for more details. + public let status: Status? + + public enum CodingKeys: String, CodingKey { + case errorReason = "error_reason" + case status = "status" + } + + /// Profiling context + /// + /// - Parameters: + /// - errorReason: The reason the Profiler encountered an error. This attribute is only present if the status is `error`. + /// + /// Possible values: + /// - `not-supported-by-browser`: The browser does not support the Profiler (i.e., `window.Profiler` is not available). + /// - `failed-to-lazy-load`: The Profiler script failed to be loaded by the browser (may be a connection issue or the chunk was not found). + /// - `missing-document-policy-header`: The Profiler failed to start because its missing `Document-Policy: js-profiling` HTTP response header. + /// - `unexpected-exception`: An exception occurred when starting the Profiler. + /// - status: Used to track the status of the RUM Profiler. + /// + /// They are defined in order of when they can happen, from the moment the SDK is initialized to the moment the Profiler is actually running. + /// + /// - `starting`: The Profiler is starting (i.e., when the SDK just started). This is the initial status. + /// - `running`: The Profiler is running. + /// - `stopped`: The Profiler is stopped. + /// - `error`: The Profiler encountered an error. See `error_reason` for more details. + public init( + errorReason: ErrorReason? = nil, + status: Status? = nil + ) { + self.errorReason = errorReason + self.status = status + } + + /// The reason the Profiler encountered an error. This attribute is only present if the status is `error`. + /// + /// Possible values: + /// - `not-supported-by-browser`: The browser does not support the Profiler (i.e., `window.Profiler` is not available). + /// - `failed-to-lazy-load`: The Profiler script failed to be loaded by the browser (may be a connection issue or the chunk was not found). + /// - `missing-document-policy-header`: The Profiler failed to start because its missing `Document-Policy: js-profiling` HTTP response header. + /// - `unexpected-exception`: An exception occurred when starting the Profiler. + public enum ErrorReason: String, Codable { + case notSupportedByBrowser = "not-supported-by-browser" + case failedToLazyLoad = "failed-to-lazy-load" + case missingDocumentPolicyHeader = "missing-document-policy-header" + case unexpectedException = "unexpected-exception" + } + + /// Used to track the status of the RUM Profiler. + /// + /// They are defined in order of when they can happen, from the moment the SDK is initialized to the moment the Profiler is actually running. + /// + /// - `starting`: The Profiler is starting (i.e., when the SDK just started). This is the initial status. + /// - `running`: The Profiler is running. + /// - `stopped`: The Profiler is stopped. + /// - `error`: The Profiler encountered an error. See `error_reason` for more details. + public enum Status: String, Codable { + case starting = "starting" + case running = "running" + case stopped = "stopped" + case error = "error" + } + } + /// Session-related internal properties public struct Session: Codable { /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) @@ -2878,6 +2989,9 @@ public struct RUMResourceEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution public struct Configuration: Codable { + /// The percentage of views profiled + public let profilingSampleRate: Double? + /// The percentage of sessions with RUM & Session Replay pricing tracked public let sessionReplaySampleRate: Double? @@ -2885,6 +2999,7 @@ public struct RUMResourceEvent: RUMDataModel { public let sessionSampleRate: Double public enum CodingKeys: String, CodingKey { + case profilingSampleRate = "profiling_sample_rate" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" } @@ -2892,12 +3007,15 @@ public struct RUMResourceEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution /// /// - Parameters: + /// - profilingSampleRate: The percentage of views profiled /// - sessionReplaySampleRate: The percentage of sessions with RUM & Session Replay pricing tracked /// - sessionSampleRate: The percentage of sessions tracked public init( + profilingSampleRate: Double? = nil, sessionReplaySampleRate: Double? = nil, sessionSampleRate: Double ) { + self.profilingSampleRate = profilingSampleRate self.sessionReplaySampleRate = sessionReplaySampleRate self.sessionSampleRate = sessionSampleRate } @@ -3845,6 +3963,9 @@ public struct RUMViewEvent: RUMDataModel { /// List of the page states during the view public let pageStates: [PageStates]? + /// Profiling context + public let profiling: Profiling? + /// Debug metadata for Replay Sessions public let replayStats: ReplayStats? @@ -3861,6 +3982,7 @@ public struct RUMViewEvent: RUMDataModel { case documentVersion = "document_version" case formatVersion = "format_version" case pageStates = "page_states" + case profiling = "profiling" case replayStats = "replay_stats" case sdkName = "sdk_name" case session = "session" @@ -3874,6 +3996,7 @@ public struct RUMViewEvent: RUMDataModel { /// - configuration: Subset of the SDK configuration options in use during its execution /// - documentVersion: Version of the update of the view event /// - pageStates: List of the page states during the view + /// - profiling: Profiling context /// - replayStats: Debug metadata for Replay Sessions /// - sdkName: SDK name (e.g. 'logs', 'rum', 'rum-slim', etc.) /// - session: Session-related internal properties @@ -3883,6 +4006,7 @@ public struct RUMViewEvent: RUMDataModel { configuration: Configuration? = nil, documentVersion: Int64, pageStates: [PageStates]? = nil, + profiling: Profiling? = nil, replayStats: ReplayStats? = nil, sdkName: String? = nil, session: Session? = nil @@ -3892,6 +4016,7 @@ public struct RUMViewEvent: RUMDataModel { self.configuration = configuration self.documentVersion = documentVersion self.pageStates = pageStates + self.profiling = profiling self.replayStats = replayStats self.sdkName = sdkName self.session = session @@ -3919,6 +4044,9 @@ public struct RUMViewEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution public struct Configuration: Codable { + /// The percentage of views profiled + public let profilingSampleRate: Double? + /// The percentage of sessions with RUM & Session Replay pricing tracked public let sessionReplaySampleRate: Double? @@ -3929,6 +4057,7 @@ public struct RUMViewEvent: RUMDataModel { public let startSessionReplayRecordingManually: Bool? public enum CodingKeys: String, CodingKey { + case profilingSampleRate = "profiling_sample_rate" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" case startSessionReplayRecordingManually = "start_session_replay_recording_manually" @@ -3937,14 +4066,17 @@ public struct RUMViewEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution /// /// - Parameters: + /// - profilingSampleRate: The percentage of views profiled /// - sessionReplaySampleRate: The percentage of sessions with RUM & Session Replay pricing tracked /// - sessionSampleRate: The percentage of sessions tracked /// - startSessionReplayRecordingManually: Whether session replay recording configured to start manually public init( + profilingSampleRate: Double? = nil, sessionReplaySampleRate: Double? = nil, sessionSampleRate: Double, startSessionReplayRecordingManually: Bool? = nil ) { + self.profilingSampleRate = profilingSampleRate self.sessionReplaySampleRate = sessionReplaySampleRate self.sessionSampleRate = sessionSampleRate self.startSessionReplayRecordingManually = startSessionReplayRecordingManually @@ -3987,6 +4119,88 @@ public struct RUMViewEvent: RUMDataModel { } } + /// Profiling context + public struct Profiling: Codable { + /// The reason the Profiler encountered an error. This attribute is only present if the status is `error`. + /// + /// Possible values: + /// - `not-supported-by-browser`: The browser does not support the Profiler (i.e., `window.Profiler` is not available). + /// - `failed-to-lazy-load`: The Profiler script failed to be loaded by the browser (may be a connection issue or the chunk was not found). + /// - `missing-document-policy-header`: The Profiler failed to start because its missing `Document-Policy: js-profiling` HTTP response header. + /// - `unexpected-exception`: An exception occurred when starting the Profiler. + public let errorReason: ErrorReason? + + /// Used to track the status of the RUM Profiler. + /// + /// They are defined in order of when they can happen, from the moment the SDK is initialized to the moment the Profiler is actually running. + /// + /// - `starting`: The Profiler is starting (i.e., when the SDK just started). This is the initial status. + /// - `running`: The Profiler is running. + /// - `stopped`: The Profiler is stopped. + /// - `error`: The Profiler encountered an error. See `error_reason` for more details. + public let status: Status? + + public enum CodingKeys: String, CodingKey { + case errorReason = "error_reason" + case status = "status" + } + + /// Profiling context + /// + /// - Parameters: + /// - errorReason: The reason the Profiler encountered an error. This attribute is only present if the status is `error`. + /// + /// Possible values: + /// - `not-supported-by-browser`: The browser does not support the Profiler (i.e., `window.Profiler` is not available). + /// - `failed-to-lazy-load`: The Profiler script failed to be loaded by the browser (may be a connection issue or the chunk was not found). + /// - `missing-document-policy-header`: The Profiler failed to start because its missing `Document-Policy: js-profiling` HTTP response header. + /// - `unexpected-exception`: An exception occurred when starting the Profiler. + /// - status: Used to track the status of the RUM Profiler. + /// + /// They are defined in order of when they can happen, from the moment the SDK is initialized to the moment the Profiler is actually running. + /// + /// - `starting`: The Profiler is starting (i.e., when the SDK just started). This is the initial status. + /// - `running`: The Profiler is running. + /// - `stopped`: The Profiler is stopped. + /// - `error`: The Profiler encountered an error. See `error_reason` for more details. + public init( + errorReason: ErrorReason? = nil, + status: Status? = nil + ) { + self.errorReason = errorReason + self.status = status + } + + /// The reason the Profiler encountered an error. This attribute is only present if the status is `error`. + /// + /// Possible values: + /// - `not-supported-by-browser`: The browser does not support the Profiler (i.e., `window.Profiler` is not available). + /// - `failed-to-lazy-load`: The Profiler script failed to be loaded by the browser (may be a connection issue or the chunk was not found). + /// - `missing-document-policy-header`: The Profiler failed to start because its missing `Document-Policy: js-profiling` HTTP response header. + /// - `unexpected-exception`: An exception occurred when starting the Profiler. + public enum ErrorReason: String, Codable { + case notSupportedByBrowser = "not-supported-by-browser" + case failedToLazyLoad = "failed-to-lazy-load" + case missingDocumentPolicyHeader = "missing-document-policy-header" + case unexpectedException = "unexpected-exception" + } + + /// Used to track the status of the RUM Profiler. + /// + /// They are defined in order of when they can happen, from the moment the SDK is initialized to the moment the Profiler is actually running. + /// + /// - `starting`: The Profiler is starting (i.e., when the SDK just started). This is the initial status. + /// - `running`: The Profiler is running. + /// - `stopped`: The Profiler is stopped. + /// - `error`: The Profiler encountered an error. See `error_reason` for more details. + public enum Status: String, Codable { + case starting = "starting" + case running = "running" + case stopped = "stopped" + case error = "error" + } + } + /// Debug metadata for Replay Sessions public struct ReplayStats: Codable { /// The number of records produced during this view lifetime @@ -5591,6 +5805,9 @@ public struct RUMVitalEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution public struct Configuration: Codable { + /// The percentage of views profiled + public let profilingSampleRate: Double? + /// The percentage of sessions with RUM & Session Replay pricing tracked public let sessionReplaySampleRate: Double? @@ -5598,6 +5815,7 @@ public struct RUMVitalEvent: RUMDataModel { public let sessionSampleRate: Double public enum CodingKeys: String, CodingKey { + case profilingSampleRate = "profiling_sample_rate" case sessionReplaySampleRate = "session_replay_sample_rate" case sessionSampleRate = "session_sample_rate" } @@ -5605,12 +5823,15 @@ public struct RUMVitalEvent: RUMDataModel { /// Subset of the SDK configuration options in use during its execution /// /// - Parameters: + /// - profilingSampleRate: The percentage of views profiled /// - sessionReplaySampleRate: The percentage of sessions with RUM & Session Replay pricing tracked /// - sessionSampleRate: The percentage of sessions tracked public init( + profilingSampleRate: Double? = nil, sessionReplaySampleRate: Double? = nil, sessionSampleRate: Double ) { + self.profilingSampleRate = profilingSampleRate self.sessionReplaySampleRate = sessionReplaySampleRate self.sessionSampleRate = sessionSampleRate } @@ -8746,23 +8967,47 @@ public struct RUMDevice: Codable { /// The CPU architecture of the device that is reporting the error public let architecture: String? + /// Current battery level of the device (0.0 to 1.0). + public let batteryLevel: Double? + /// Device marketing brand, e.g. Apple, OPPO, Xiaomi, etc. public let brand: String? + /// Current screen brightness level (0.0 to 1.0). + public let brightnessLevel: Double? + + /// The user’s locale as a language tag combining language and region, e.g. 'en-US'. + public let locale: String? + + /// Ordered list of the user’s preferred system languages as IETF language tags. + public let locales: [String]? + /// Device SKU model, e.g. Samsung SM-988GN, etc. Quite often name and model can be the same. public let model: String? /// Device marketing name, e.g. Xiaomi Redmi Note 8 Pro, Pixel 5, etc. public let name: String? + /// Whether the device is in power saving mode. + public let powerSavingMode: Bool? + + /// The device’s current time zone identifier, e.g. 'Europe/Berlin'. + public let timeZone: String? + /// Device type info - public let type: RUMDeviceType + public let type: RUMDeviceType? public enum CodingKeys: String, CodingKey { case architecture = "architecture" + case batteryLevel = "battery_level" case brand = "brand" + case brightnessLevel = "brightness_level" + case locale = "locale" + case locales = "locales" case model = "model" case name = "name" + case powerSavingMode = "power_saving_mode" + case timeZone = "time_zone" case type = "type" } @@ -8770,21 +9015,39 @@ public struct RUMDevice: Codable { /// /// - Parameters: /// - architecture: The CPU architecture of the device that is reporting the error + /// - batteryLevel: Current battery level of the device (0.0 to 1.0). /// - brand: Device marketing brand, e.g. Apple, OPPO, Xiaomi, etc. + /// - brightnessLevel: Current screen brightness level (0.0 to 1.0). + /// - locale: The user’s locale as a language tag combining language and region, e.g. 'en-US'. + /// - locales: Ordered list of the user’s preferred system languages as IETF language tags. /// - model: Device SKU model, e.g. Samsung SM-988GN, etc. Quite often name and model can be the same. /// - name: Device marketing name, e.g. Xiaomi Redmi Note 8 Pro, Pixel 5, etc. + /// - powerSavingMode: Whether the device is in power saving mode. + /// - timeZone: The device’s current time zone identifier, e.g. 'Europe/Berlin'. /// - type: Device type info public init( architecture: String? = nil, + batteryLevel: Double? = nil, brand: String? = nil, + brightnessLevel: Double? = nil, + locale: String? = nil, + locales: [String]? = nil, model: String? = nil, name: String? = nil, - type: RUMDeviceType + powerSavingMode: Bool? = nil, + timeZone: String? = nil, + type: RUMDeviceType? = nil ) { self.architecture = architecture + self.batteryLevel = batteryLevel self.brand = brand + self.brightnessLevel = brightnessLevel + self.locale = locale + self.locales = locales self.model = model self.name = name + self.powerSavingMode = powerSavingMode + self.timeZone = timeZone self.type = type } @@ -9090,4 +9353,4 @@ public struct RUMTelemetryOperatingSystem: Codable { } } -// Generated from https://github.com/DataDog/rum-events-format/tree/b8d694987f0873dfafa9248ef40b9a5ba56f7101 +// Generated from https://github.com/DataDog/rum-events-format/tree/fc1a8bd02785f5a108f5afa64c0e7a61aa89c203 diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index 70f05214db..ecd04c32cd 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -245,6 +245,10 @@ public class DDRUMActionEventDDConfiguration: NSObject { self.root = root } + @objc public var profilingSampleRate: NSNumber? { + root.swiftModel.dd.configuration!.profilingSampleRate as NSNumber? + } + @objc public var sessionReplaySampleRate: NSNumber? { root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? } @@ -800,10 +804,26 @@ public class DDRUMActionEventRUMDevice: NSObject { root.swiftModel.device!.architecture } + @objc public var batteryLevel: NSNumber? { + root.swiftModel.device!.batteryLevel as NSNumber? + } + @objc public var brand: String? { root.swiftModel.device!.brand } + @objc public var brightnessLevel: NSNumber? { + root.swiftModel.device!.brightnessLevel as NSNumber? + } + + @objc public var locale: String? { + root.swiftModel.device!.locale + } + + @objc public var locales: [String]? { + root.swiftModel.device!.locales + } + @objc public var model: String? { root.swiftModel.device!.model } @@ -812,6 +832,14 @@ public class DDRUMActionEventRUMDevice: NSObject { root.swiftModel.device!.name } + @objc public var powerSavingMode: NSNumber? { + root.swiftModel.device!.powerSavingMode as NSNumber? + } + + @objc public var timeZone: String? { + root.swiftModel.device!.timeZone + } + @objc public var type: DDRUMActionEventRUMDeviceRUMDeviceType { .init(swift: root.swiftModel.device!.type) } @@ -819,20 +847,22 @@ public class DDRUMActionEventRUMDevice: NSObject { @objc public enum DDRUMActionEventRUMDeviceRUMDeviceType: Int { - internal init(swift: RUMDevice.RUMDeviceType) { + internal init(swift: RUMDevice.RUMDeviceType?) { switch swift { - case .mobile: self = .mobile - case .desktop: self = .desktop - case .tablet: self = .tablet - case .tv: self = .tv - case .gamingConsole: self = .gamingConsole - case .bot: self = .bot - case .other: self = .other + case nil: self = .none + case .mobile?: self = .mobile + case .desktop?: self = .desktop + case .tablet?: self = .tablet + case .tv?: self = .tv + case .gamingConsole?: self = .gamingConsole + case .bot?: self = .bot + case .other?: self = .other } } - internal var toSwift: RUMDevice.RUMDeviceType { + internal var toSwift: RUMDevice.RUMDeviceType? { switch self { + case .none: return nil case .mobile: return .mobile case .desktop: return .desktop case .tablet: return .tablet @@ -843,6 +873,7 @@ public enum DDRUMActionEventRUMDeviceRUMDeviceType: Int { } } + case none case mobile case desktop case tablet @@ -1222,6 +1253,10 @@ public class DDRUMErrorEventDDConfiguration: NSObject { self.root = root } + @objc public var profilingSampleRate: NSNumber? { + root.swiftModel.dd.configuration!.profilingSampleRate as NSNumber? + } + @objc public var sessionReplaySampleRate: NSNumber? { root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? } @@ -1625,10 +1660,26 @@ public class DDRUMErrorEventRUMDevice: NSObject { root.swiftModel.device!.architecture } + @objc public var batteryLevel: NSNumber? { + root.swiftModel.device!.batteryLevel as NSNumber? + } + @objc public var brand: String? { root.swiftModel.device!.brand } + @objc public var brightnessLevel: NSNumber? { + root.swiftModel.device!.brightnessLevel as NSNumber? + } + + @objc public var locale: String? { + root.swiftModel.device!.locale + } + + @objc public var locales: [String]? { + root.swiftModel.device!.locales + } + @objc public var model: String? { root.swiftModel.device!.model } @@ -1637,6 +1688,14 @@ public class DDRUMErrorEventRUMDevice: NSObject { root.swiftModel.device!.name } + @objc public var powerSavingMode: NSNumber? { + root.swiftModel.device!.powerSavingMode as NSNumber? + } + + @objc public var timeZone: String? { + root.swiftModel.device!.timeZone + } + @objc public var type: DDRUMErrorEventRUMDeviceRUMDeviceType { .init(swift: root.swiftModel.device!.type) } @@ -1644,20 +1703,22 @@ public class DDRUMErrorEventRUMDevice: NSObject { @objc public enum DDRUMErrorEventRUMDeviceRUMDeviceType: Int { - internal init(swift: RUMDevice.RUMDeviceType) { + internal init(swift: RUMDevice.RUMDeviceType?) { switch swift { - case .mobile: self = .mobile - case .desktop: self = .desktop - case .tablet: self = .tablet - case .tv: self = .tv - case .gamingConsole: self = .gamingConsole - case .bot: self = .bot - case .other: self = .other + case nil: self = .none + case .mobile?: self = .mobile + case .desktop?: self = .desktop + case .tablet?: self = .tablet + case .tv?: self = .tv + case .gamingConsole?: self = .gamingConsole + case .bot?: self = .bot + case .other?: self = .other } } - internal var toSwift: RUMDevice.RUMDeviceType { + internal var toSwift: RUMDevice.RUMDeviceType? { switch self { + case .none: return nil case .mobile: return .mobile case .desktop: return .desktop case .tablet: return .tablet @@ -1668,6 +1729,7 @@ public enum DDRUMErrorEventRUMDeviceRUMDeviceType: Int { } } + case none case mobile case desktop case tablet @@ -1840,6 +1902,7 @@ public enum DDRUMErrorEventErrorCategory: Int { case .exception?: self = .exception case .watchdogTermination?: self = .watchdogTermination case .memoryWarning?: self = .memoryWarning + case .network?: self = .network } } @@ -1851,6 +1914,7 @@ public enum DDRUMErrorEventErrorCategory: Int { case .exception: return .exception case .watchdogTermination: return .watchdogTermination case .memoryWarning: return .memoryWarning + case .network: return .network } } @@ -1860,6 +1924,7 @@ public enum DDRUMErrorEventErrorCategory: Int { case exception case watchdogTermination case memoryWarning + case network } @objc @@ -2625,6 +2690,10 @@ public class DDRUMLongTaskEventDD: NSObject { root.swiftModel.dd.formatVersion as NSNumber } + @objc public var profiling: DDRUMLongTaskEventDDProfiling? { + root.swiftModel.dd.profiling != nil ? DDRUMLongTaskEventDDProfiling(root: root) : nil + } + @objc public var sdkName: String? { root.swiftModel.dd.sdkName } @@ -2642,6 +2711,10 @@ public class DDRUMLongTaskEventDDConfiguration: NSObject { self.root = root } + @objc public var profilingSampleRate: NSNumber? { + root.swiftModel.dd.configuration!.profilingSampleRate as NSNumber? + } + @objc public var sessionReplaySampleRate: NSNumber? { root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? } @@ -2651,6 +2724,81 @@ public class DDRUMLongTaskEventDDConfiguration: NSObject { } } +@objc +public class DDRUMLongTaskEventDDProfiling: NSObject { + internal let root: DDRUMLongTaskEvent + + internal init(root: DDRUMLongTaskEvent) { + self.root = root + } + + @objc public var errorReason: DDRUMLongTaskEventDDProfilingErrorReason { + .init(swift: root.swiftModel.dd.profiling!.errorReason) + } + + @objc public var status: DDRUMLongTaskEventDDProfilingStatus { + .init(swift: root.swiftModel.dd.profiling!.status) + } +} + +@objc +public enum DDRUMLongTaskEventDDProfilingErrorReason: Int { + internal init(swift: RUMLongTaskEvent.DD.Profiling.ErrorReason?) { + switch swift { + case nil: self = .none + case .notSupportedByBrowser?: self = .notSupportedByBrowser + case .failedToLazyLoad?: self = .failedToLazyLoad + case .missingDocumentPolicyHeader?: self = .missingDocumentPolicyHeader + case .unexpectedException?: self = .unexpectedException + } + } + + internal var toSwift: RUMLongTaskEvent.DD.Profiling.ErrorReason? { + switch self { + case .none: return nil + case .notSupportedByBrowser: return .notSupportedByBrowser + case .failedToLazyLoad: return .failedToLazyLoad + case .missingDocumentPolicyHeader: return .missingDocumentPolicyHeader + case .unexpectedException: return .unexpectedException + } + } + + case none + case notSupportedByBrowser + case failedToLazyLoad + case missingDocumentPolicyHeader + case unexpectedException +} + +@objc +public enum DDRUMLongTaskEventDDProfilingStatus: Int { + internal init(swift: RUMLongTaskEvent.DD.Profiling.Status?) { + switch swift { + case nil: self = .none + case .starting?: self = .starting + case .running?: self = .running + case .stopped?: self = .stopped + case .error?: self = .error + } + } + + internal var toSwift: RUMLongTaskEvent.DD.Profiling.Status? { + switch self { + case .none: return nil + case .starting: return .starting + case .running: return .running + case .stopped: return .stopped + case .error: return .error + } + } + + case none + case starting + case running + case stopped + case error +} + @objc public class DDRUMLongTaskEventDDSession: NSObject { internal let root: DDRUMLongTaskEvent @@ -3045,10 +3193,26 @@ public class DDRUMLongTaskEventRUMDevice: NSObject { root.swiftModel.device!.architecture } + @objc public var batteryLevel: NSNumber? { + root.swiftModel.device!.batteryLevel as NSNumber? + } + @objc public var brand: String? { root.swiftModel.device!.brand } + @objc public var brightnessLevel: NSNumber? { + root.swiftModel.device!.brightnessLevel as NSNumber? + } + + @objc public var locale: String? { + root.swiftModel.device!.locale + } + + @objc public var locales: [String]? { + root.swiftModel.device!.locales + } + @objc public var model: String? { root.swiftModel.device!.model } @@ -3057,6 +3221,14 @@ public class DDRUMLongTaskEventRUMDevice: NSObject { root.swiftModel.device!.name } + @objc public var powerSavingMode: NSNumber? { + root.swiftModel.device!.powerSavingMode as NSNumber? + } + + @objc public var timeZone: String? { + root.swiftModel.device!.timeZone + } + @objc public var type: DDRUMLongTaskEventRUMDeviceRUMDeviceType { .init(swift: root.swiftModel.device!.type) } @@ -3064,20 +3236,22 @@ public class DDRUMLongTaskEventRUMDevice: NSObject { @objc public enum DDRUMLongTaskEventRUMDeviceRUMDeviceType: Int { - internal init(swift: RUMDevice.RUMDeviceType) { + internal init(swift: RUMDevice.RUMDeviceType?) { switch swift { - case .mobile: self = .mobile - case .desktop: self = .desktop - case .tablet: self = .tablet - case .tv: self = .tv - case .gamingConsole: self = .gamingConsole - case .bot: self = .bot - case .other: self = .other + case nil: self = .none + case .mobile?: self = .mobile + case .desktop?: self = .desktop + case .tablet?: self = .tablet + case .tv?: self = .tv + case .gamingConsole?: self = .gamingConsole + case .bot?: self = .bot + case .other?: self = .other } } - internal var toSwift: RUMDevice.RUMDeviceType { + internal var toSwift: RUMDevice.RUMDeviceType? { switch self { + case .none: return nil case .mobile: return .mobile case .desktop: return .desktop case .tablet: return .tablet @@ -3088,6 +3262,7 @@ public enum DDRUMLongTaskEventRUMDeviceRUMDeviceType: Int { } } + case none case mobile case desktop case tablet @@ -3632,6 +3807,10 @@ public class DDRUMResourceEventDDConfiguration: NSObject { self.root = root } + @objc public var profilingSampleRate: NSNumber? { + root.swiftModel.dd.configuration!.profilingSampleRate as NSNumber? + } + @objc public var sessionReplaySampleRate: NSNumber? { root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? } @@ -4035,10 +4214,26 @@ public class DDRUMResourceEventRUMDevice: NSObject { root.swiftModel.device!.architecture } + @objc public var batteryLevel: NSNumber? { + root.swiftModel.device!.batteryLevel as NSNumber? + } + @objc public var brand: String? { root.swiftModel.device!.brand } + @objc public var brightnessLevel: NSNumber? { + root.swiftModel.device!.brightnessLevel as NSNumber? + } + + @objc public var locale: String? { + root.swiftModel.device!.locale + } + + @objc public var locales: [String]? { + root.swiftModel.device!.locales + } + @objc public var model: String? { root.swiftModel.device!.model } @@ -4047,6 +4242,14 @@ public class DDRUMResourceEventRUMDevice: NSObject { root.swiftModel.device!.name } + @objc public var powerSavingMode: NSNumber? { + root.swiftModel.device!.powerSavingMode as NSNumber? + } + + @objc public var timeZone: String? { + root.swiftModel.device!.timeZone + } + @objc public var type: DDRUMResourceEventRUMDeviceRUMDeviceType { .init(swift: root.swiftModel.device!.type) } @@ -4054,20 +4257,22 @@ public class DDRUMResourceEventRUMDevice: NSObject { @objc public enum DDRUMResourceEventRUMDeviceRUMDeviceType: Int { - internal init(swift: RUMDevice.RUMDeviceType) { + internal init(swift: RUMDevice.RUMDeviceType?) { switch swift { - case .mobile: self = .mobile - case .desktop: self = .desktop - case .tablet: self = .tablet - case .tv: self = .tv - case .gamingConsole: self = .gamingConsole - case .bot: self = .bot - case .other: self = .other + case nil: self = .none + case .mobile?: self = .mobile + case .desktop?: self = .desktop + case .tablet?: self = .tablet + case .tv?: self = .tv + case .gamingConsole?: self = .gamingConsole + case .bot?: self = .bot + case .other?: self = .other } } - internal var toSwift: RUMDevice.RUMDeviceType { + internal var toSwift: RUMDevice.RUMDeviceType? { switch self { + case .none: return nil case .mobile: return .mobile case .desktop: return .desktop case .tablet: return .tablet @@ -4078,6 +4283,7 @@ public enum DDRUMResourceEventRUMDeviceRUMDeviceType: Int { } } + case none case mobile case desktop case tablet @@ -4927,6 +5133,10 @@ public class DDRUMViewEventDD: NSObject { root.swiftModel.dd.pageStates?.map { DDRUMViewEventDDPageStates(swiftModel: $0) } } + @objc public var profiling: DDRUMViewEventDDProfiling? { + root.swiftModel.dd.profiling != nil ? DDRUMViewEventDDProfiling(root: root) : nil + } + @objc public var replayStats: DDRUMViewEventDDReplayStats? { root.swiftModel.dd.replayStats != nil ? DDRUMViewEventDDReplayStats(root: root) : nil } @@ -4961,6 +5171,10 @@ public class DDRUMViewEventDDConfiguration: NSObject { self.root = root } + @objc public var profilingSampleRate: NSNumber? { + root.swiftModel.dd.configuration!.profilingSampleRate as NSNumber? + } + @objc public var sessionReplaySampleRate: NSNumber? { root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? } @@ -5021,6 +5235,81 @@ public enum DDRUMViewEventDDPageStatesState: Int { case terminated } +@objc +public class DDRUMViewEventDDProfiling: NSObject { + internal let root: DDRUMViewEvent + + internal init(root: DDRUMViewEvent) { + self.root = root + } + + @objc public var errorReason: DDRUMViewEventDDProfilingErrorReason { + .init(swift: root.swiftModel.dd.profiling!.errorReason) + } + + @objc public var status: DDRUMViewEventDDProfilingStatus { + .init(swift: root.swiftModel.dd.profiling!.status) + } +} + +@objc +public enum DDRUMViewEventDDProfilingErrorReason: Int { + internal init(swift: RUMViewEvent.DD.Profiling.ErrorReason?) { + switch swift { + case nil: self = .none + case .notSupportedByBrowser?: self = .notSupportedByBrowser + case .failedToLazyLoad?: self = .failedToLazyLoad + case .missingDocumentPolicyHeader?: self = .missingDocumentPolicyHeader + case .unexpectedException?: self = .unexpectedException + } + } + + internal var toSwift: RUMViewEvent.DD.Profiling.ErrorReason? { + switch self { + case .none: return nil + case .notSupportedByBrowser: return .notSupportedByBrowser + case .failedToLazyLoad: return .failedToLazyLoad + case .missingDocumentPolicyHeader: return .missingDocumentPolicyHeader + case .unexpectedException: return .unexpectedException + } + } + + case none + case notSupportedByBrowser + case failedToLazyLoad + case missingDocumentPolicyHeader + case unexpectedException +} + +@objc +public enum DDRUMViewEventDDProfilingStatus: Int { + internal init(swift: RUMViewEvent.DD.Profiling.Status?) { + switch swift { + case nil: self = .none + case .starting?: self = .starting + case .running?: self = .running + case .stopped?: self = .stopped + case .error?: self = .error + } + } + + internal var toSwift: RUMViewEvent.DD.Profiling.Status? { + switch self { + case .none: return nil + case .starting: return .starting + case .running: return .running + case .stopped: return .stopped + case .error: return .error + } + } + + case none + case starting + case running + case stopped + case error +} + @objc public class DDRUMViewEventDDReplayStats: NSObject { internal let root: DDRUMViewEvent @@ -5400,10 +5689,26 @@ public class DDRUMViewEventRUMDevice: NSObject { root.swiftModel.device!.architecture } + @objc public var batteryLevel: NSNumber? { + root.swiftModel.device!.batteryLevel as NSNumber? + } + @objc public var brand: String? { root.swiftModel.device!.brand } + @objc public var brightnessLevel: NSNumber? { + root.swiftModel.device!.brightnessLevel as NSNumber? + } + + @objc public var locale: String? { + root.swiftModel.device!.locale + } + + @objc public var locales: [String]? { + root.swiftModel.device!.locales + } + @objc public var model: String? { root.swiftModel.device!.model } @@ -5412,6 +5717,14 @@ public class DDRUMViewEventRUMDevice: NSObject { root.swiftModel.device!.name } + @objc public var powerSavingMode: NSNumber? { + root.swiftModel.device!.powerSavingMode as NSNumber? + } + + @objc public var timeZone: String? { + root.swiftModel.device!.timeZone + } + @objc public var type: DDRUMViewEventRUMDeviceRUMDeviceType { .init(swift: root.swiftModel.device!.type) } @@ -5419,20 +5732,22 @@ public class DDRUMViewEventRUMDevice: NSObject { @objc public enum DDRUMViewEventRUMDeviceRUMDeviceType: Int { - internal init(swift: RUMDevice.RUMDeviceType) { + internal init(swift: RUMDevice.RUMDeviceType?) { switch swift { - case .mobile: self = .mobile - case .desktop: self = .desktop - case .tablet: self = .tablet - case .tv: self = .tv - case .gamingConsole: self = .gamingConsole - case .bot: self = .bot - case .other: self = .other + case nil: self = .none + case .mobile?: self = .mobile + case .desktop?: self = .desktop + case .tablet?: self = .tablet + case .tv?: self = .tv + case .gamingConsole?: self = .gamingConsole + case .bot?: self = .bot + case .other?: self = .other } } - internal var toSwift: RUMDevice.RUMDeviceType { + internal var toSwift: RUMDevice.RUMDeviceType? { switch self { + case .none: return nil case .mobile: return .mobile case .desktop: return .desktop case .tablet: return .tablet @@ -5443,6 +5758,7 @@ public enum DDRUMViewEventRUMDeviceRUMDeviceType: Int { } } + case none case mobile case desktop case tablet @@ -6526,6 +6842,10 @@ public class DDRUMVitalEventDDConfiguration: NSObject { self.root = root } + @objc public var profilingSampleRate: NSNumber? { + root.swiftModel.dd.configuration!.profilingSampleRate as NSNumber? + } + @objc public var sessionReplaySampleRate: NSNumber? { root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber? } @@ -6906,10 +7226,26 @@ public class DDRUMVitalEventRUMDevice: NSObject { root.swiftModel.device!.architecture } + @objc public var batteryLevel: NSNumber? { + root.swiftModel.device!.batteryLevel as NSNumber? + } + @objc public var brand: String? { root.swiftModel.device!.brand } + @objc public var brightnessLevel: NSNumber? { + root.swiftModel.device!.brightnessLevel as NSNumber? + } + + @objc public var locale: String? { + root.swiftModel.device!.locale + } + + @objc public var locales: [String]? { + root.swiftModel.device!.locales + } + @objc public var model: String? { root.swiftModel.device!.model } @@ -6918,6 +7254,14 @@ public class DDRUMVitalEventRUMDevice: NSObject { root.swiftModel.device!.name } + @objc public var powerSavingMode: NSNumber? { + root.swiftModel.device!.powerSavingMode as NSNumber? + } + + @objc public var timeZone: String? { + root.swiftModel.device!.timeZone + } + @objc public var type: DDRUMVitalEventRUMDeviceRUMDeviceType { .init(swift: root.swiftModel.device!.type) } @@ -6925,20 +7269,22 @@ public class DDRUMVitalEventRUMDevice: NSObject { @objc public enum DDRUMVitalEventRUMDeviceRUMDeviceType: Int { - internal init(swift: RUMDevice.RUMDeviceType) { + internal init(swift: RUMDevice.RUMDeviceType?) { switch swift { - case .mobile: self = .mobile - case .desktop: self = .desktop - case .tablet: self = .tablet - case .tv: self = .tv - case .gamingConsole: self = .gamingConsole - case .bot: self = .bot - case .other: self = .other + case nil: self = .none + case .mobile?: self = .mobile + case .desktop?: self = .desktop + case .tablet?: self = .tablet + case .tv?: self = .tv + case .gamingConsole?: self = .gamingConsole + case .bot?: self = .bot + case .other?: self = .other } } - internal var toSwift: RUMDevice.RUMDeviceType { + internal var toSwift: RUMDevice.RUMDeviceType? { switch self { + case .none: return nil case .mobile: return .mobile case .desktop: return .desktop case .tablet: return .tablet @@ -6949,6 +7295,7 @@ public enum DDRUMVitalEventRUMDeviceRUMDeviceType: Int { } } + case none case mobile case desktop case tablet @@ -8562,4 +8909,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/b8d694987f0873dfafa9248ef40b9a5ba56f7101 +// Generated from https://github.com/DataDog/rum-events-format/tree/fc1a8bd02785f5a108f5afa64c0e7a61aa89c203 diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index 806f5b9a12..8c36d5633c 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -193,7 +193,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { } /// If the crash occurred before starting RUM session (after initializing SDK, but before starting the first view) we don't have any session UUID to associate the error with. - /// In that case, we consider sending this crash within a new, single-view session: either "ApplicationLaunch" view or "Background" view. + /// In that case, we consider sending this crash within a new, single-view session: either "ApplicationLaunch" view or "Background" view. private func sendCrashReportToNewSession( _ crashReport: DDCrashReport, crashContext: CrashContext, @@ -331,7 +331,13 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { // See https://github.com/DataDog/dd-sdk-ios/pull/1834 for more context. context: context.lastRUMAttributes, date: startDate.timeIntervalSince1970.toInt64Milliseconds, - device: .init(device: context.device, telemetry: featureScope.telemetry), + device: .init( + device: context.device, + batteryLevel: Double(context.batteryStatus?.level ?? 0), + brightnessLevel: Double(context.brightnessLevel ?? 0), + powerSavingMode: context.isLowPowerModeEnabled, + telemetry: featureScope.telemetry + ), display: nil, // RUMM-2197: In very rare cases, the OS info computed below might not be exactly the one // that the app crashed on. This would correspond to a scenario when the device OS was upgraded diff --git a/DatadogRUM/Sources/RUMEvent/RUMDeviceInfo.swift b/DatadogRUM/Sources/RUMEvent/RUMDeviceInfo.swift index d58d975912..e92961062f 100644 --- a/DatadogRUM/Sources/RUMEvent/RUMDeviceInfo.swift +++ b/DatadogRUM/Sources/RUMEvent/RUMDeviceInfo.swift @@ -14,19 +14,28 @@ extension RUMDevice { ) { self.init( device: context.device, + batteryLevel: Double(context.batteryStatus?.level ?? 0), + brightnessLevel: Double(context.brightnessLevel ?? 0), + powerSavingMode: context.isLowPowerModeEnabled, telemetry: telemetry ) } init( device: DeviceInfo, + batteryLevel: Double, + brightnessLevel: Double, + powerSavingMode: Bool, telemetry: Telemetry = NOPTelemetry() ) { self.init( architecture: device.architecture, + batteryLevel: batteryLevel, brand: device.brand, + brightnessLevel: brightnessLevel, model: device.model, name: device.name, + powerSavingMode: powerSavingMode, type: { switch device.type { case .iPhone, .iPod: return .mobile diff --git a/DatadogRUM/Tests/RUMEvent/RUMDeviceInfoTests.swift b/DatadogRUM/Tests/RUMEvent/RUMDeviceInfoTests.swift index f1e9b64700..d4c17cf177 100644 --- a/DatadogRUM/Tests/RUMEvent/RUMDeviceInfoTests.swift +++ b/DatadogRUM/Tests/RUMEvent/RUMDeviceInfoTests.swift @@ -15,8 +15,15 @@ class RUMDeviceInfoTests: XCTestCase { let randomName: String = .mockRandom() let randomArch: String = .mockRandom() + let batteryLevel: Double = .mockRandom() + let brightnessLevel: Double = .mockRandom() + let powerSavingMode: Bool = .mockRandom() + let info = RUMDevice( - device: .mockWith(name: randomName, model: randomModel, architecture: randomArch) + device: .mockWith(name: randomName, model: randomModel, architecture: randomArch), + batteryLevel: batteryLevel, + brightnessLevel: brightnessLevel, + powerSavingMode: powerSavingMode ) XCTAssertEqual(info.brand, "Apple") diff --git a/TestUtilities/Sources/Mocks/CrashReporting/CrashReportingFeatureMocks.swift b/TestUtilities/Sources/Mocks/CrashReporting/CrashReportingFeatureMocks.swift index 095a5823c8..39be445d01 100644 --- a/TestUtilities/Sources/Mocks/CrashReporting/CrashReportingFeatureMocks.swift +++ b/TestUtilities/Sources/Mocks/CrashReporting/CrashReportingFeatureMocks.swift @@ -141,6 +141,9 @@ extension CrashContext { accountInfo: AccountInfo? = nil, networkConnectionInfo: NetworkConnectionInfo? = .mockAny(), carrierInfo: CarrierInfo? = .mockAny(), + batteryStatus: BatteryStatus? = .mockAny(), + brightnessLevel: BrightnessLevel? = .mockAny(), + isLowPowerModeEnabled: Bool = .mockAny(), lastRUMViewEvent: RUMViewEvent? = nil, lastRUMSessionState: RUMSessionState? = nil, lastIsAppInForeground: Bool = .mockAny(), @@ -162,6 +165,9 @@ extension CrashContext { accountInfo: accountInfo, networkConnectionInfo: networkConnectionInfo, carrierInfo: carrierInfo, + batteryStatus: batteryStatus, + brightnessLevel: brightnessLevel, + isLowPowerModeEnabled: isLowPowerModeEnabled, lastIsAppInForeground: lastIsAppInForeground, appLaunchDate: appLaunchDate, lastRUMViewEvent: lastRUMViewEvent, @@ -186,6 +192,9 @@ extension CrashContext { accountInfo: .mockRandom(), networkConnectionInfo: .mockRandom(), carrierInfo: .mockRandom(), + batteryStatus: .mockRandom(), + brightnessLevel: .mockRandom(), + isLowPowerModeEnabled: .mockRandom(), lastIsAppInForeground: .mockRandom(), appLaunchDate: .mockRandomInThePast(), lastRUMViewEvent: .mockRandom(), diff --git a/TestUtilities/Sources/Mocks/DatadogInternal/DatadogContextMock.swift b/TestUtilities/Sources/Mocks/DatadogInternal/DatadogContextMock.swift index 1864a6f9f3..d1001373b0 100644 --- a/TestUtilities/Sources/Mocks/DatadogInternal/DatadogContextMock.swift +++ b/TestUtilities/Sources/Mocks/DatadogInternal/DatadogContextMock.swift @@ -346,7 +346,7 @@ extension CarrierInfo.RadioAccessTechnology: RandomMockable { } } -extension BatteryStatus { +extension BatteryStatus: AnyMockable, RandomMockable { public static func mockAny() -> BatteryStatus { return mockWith() } @@ -357,8 +357,31 @@ extension BatteryStatus { ) -> BatteryStatus { return BatteryStatus(state: state, level: level) } + + public static func mockRandom() -> BatteryStatus { + return BatteryStatus( + state: [.unknown, .unplugged, .charging, .full].randomElement() ?? .unknown, + level: Float.random(in: 0.0...1.0) + ) + } } +//extension BrightnessLevel: AnyMockable, RandomMockable { +// public static func mockAny() -> BrightnessLevel { +// return mockWith() +// } +// +// public static func mockWith( +// level: Float = 0.5 +// ) -> BrightnessLevel { +// return level +// } +// +// public static func mockRandom() -> BrightnessLevel { +// return Float.random(in: 0.0...1.0) +// } +//} + extension TrackingConsent { public static func mockRandom() -> TrackingConsent { return [.granted, .notGranted, .pending].randomElement()! diff --git a/TestUtilities/Sources/Mocks/SystemFrameworks/UIKitMocks.swift b/TestUtilities/Sources/Mocks/SystemFrameworks/UIKitMocks.swift index 81402d63e1..c8337c6958 100644 --- a/TestUtilities/Sources/Mocks/SystemFrameworks/UIKitMocks.swift +++ b/TestUtilities/Sources/Mocks/SystemFrameworks/UIKitMocks.swift @@ -64,6 +64,21 @@ public class UIDeviceMock: UIDevice { #endif } +#if os(iOS) +public class UIScreenMock: UIScreen { + private var _brightness: CGFloat + + public init(brightness: CGFloat = 0.5) { + self._brightness = brightness + } + + override public var brightness: CGFloat { + get { _brightness } + set { _brightness = newValue } + } +} +#endif + #if !os(tvOS) extension UIDevice.BatteryState: AnyMockable { public static func mockAny() -> UIDevice.BatteryState {