From 4637a8f14bba0d870c5ae4c3b91750f32fe5aa44 Mon Sep 17 00:00:00 2001 From: Sergio Barrio Date: Fri, 28 Mar 2025 11:32:18 +0000 Subject: [PATCH] Added initialResourceThreshold configuration property to enable custom thresholds on TNS --- .../datadog/reactnative/DdSdkConfiguration.kt | 7 +- .../reactnative/DdSdkConfigurationExt.kt | 9 +- .../reactnative/DdSdkNativeInitialization.kt | 7 + .../DdSdkNativeInitializationTest.kt | 2 + .../com/datadog/reactnative/DdSdkTest.kt | 77 ++++ .../tools/unit/DdSdkConfigurationExt.kt | 6 + .../input/complete-configuration.json | 3 +- .../core/datadog-configuration.schema.json | 4 + .../core/ios/Sources/DdSdkConfiguration.swift | 6 +- .../Sources/DdSdkNativeInitialization.swift | 6 + .../ios/Sources/RNDdSdkConfiguration.swift | 10 +- .../DdSdkNativeInitializationTests.swift | 2 + packages/core/ios/Tests/DdSdkTests.swift | 400 ++++++++++++------ .../Fixtures/complete-configuration.json | 3 +- packages/core/src/DdSdkReactNative.tsx | 3 +- .../src/DdSdkReactNativeConfiguration.tsx | 9 + .../src/__tests__/DdSdkReactNative.test.tsx | 23 + .../DdSdkReactNativeConfiguration.test.ts | 4 +- .../__tests__/initialization.test.tsx | 1 + packages/core/src/types.tsx | 5 +- 20 files changed, 454 insertions(+), 133 deletions(-) diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfiguration.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfiguration.kt index 155993415..bc598d005 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfiguration.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfiguration.kt @@ -42,6 +42,7 @@ import java.net.Proxy * @param bundleLogsWithTraces Enables Traces correlation with logs. * @param trackNonFatalAnrs Enables tracking of non-fatal ANRs on Android. * @param batchProcessingLevel The preferred number of batches of data that will be sent in a single upload (can be 'LOW', 'MEDIUM' (default), 'HIGH') + * @param initialResourceThreshold "The amount of time after a view starts where a Resource should be considered when calculating Time to Network-Settled (TNS)" */ data class DdSdkConfiguration( val clientToken: String, @@ -71,7 +72,8 @@ data class DdSdkConfiguration( val bundleLogsWithRum: Boolean? = null, val bundleLogsWithTraces: Boolean? = null, val trackNonFatalAnrs: Boolean? = null, - val batchProcessingLevel: String? = null + val batchProcessingLevel: String? = null, + val initialResourceThreshold: Double? = null ) internal data class JSONConfigurationFile( @@ -104,7 +106,8 @@ internal data class JSONDdSdkConfiguration( val bundleLogsWithRum: Boolean? = null, val bundleLogsWithTraces: Boolean? = null, val trackNonFatalAnrs: Boolean? = null, - val batchProcessingLevel: String? = null + val batchProcessingLevel: String? = null, + val initialResourceThreshold: Double? = null ) internal data class JSONProxyConfiguration( diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfigurationExt.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfigurationExt.kt index b0eafd907..0c36e1797 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfigurationExt.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkConfigurationExt.kt @@ -48,7 +48,8 @@ internal fun ReadableMap.asDdSdkConfiguration(): DdSdkConfiguration { bundleLogsWithRum = getBoolean("bundleLogsWithRum"), bundleLogsWithTraces = getBoolean("bundleLogsWithTraces"), trackNonFatalAnrs = getBooleanOrNull("trackNonFatalAnrs"), - batchProcessingLevel = getString("batchProcessingLevel") + batchProcessingLevel = getString("batchProcessingLevel"), + initialResourceThreshold = getDouble("initialResourceThreshold") ) } @@ -138,6 +139,7 @@ internal object DefaultConfiguration { const val trackBackgroundEvents = false const val bundleLogsWithRum = true const val bundleLogsWithTraces = true + const val initialResourceThreshold = 0.1 } @Suppress("ComplexMethod") @@ -172,7 +174,9 @@ internal fun JSONDdSdkConfiguration.asDdSdkConfiguration(): DdSdkConfiguration { this.firstPartyHosts?.asFirstPartyHosts(), this.bundleLogsWithRum ?: DefaultConfiguration.bundleLogsWithRum, this.bundleLogsWithTraces ?: DefaultConfiguration.bundleLogsWithTraces, - this.trackNonFatalAnrs + this.trackNonFatalAnrs, + this.batchProcessingLevel, + this.initialResourceThreshold ?: DefaultConfiguration.initialResourceThreshold ) } @@ -236,6 +240,7 @@ internal fun DdSdkConfiguration.toReadableMap(): ReadableMap { trackBackgroundEvents?.let { map.putBoolean("trackBackgroundEvents", it) } trackNonFatalAnrs?.let { map.putBoolean("trackNonFatalAnrs", it) } additionalConfig?.let { map.putMap("additionalConfig", it.toWritableMap()) } + initialResourceThreshold?.let { map.putDouble("initialResourceThreshold", it)} return map } diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt index 3e2986cd1..a3a793d6d 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdSdkNativeInitialization.kt @@ -21,6 +21,7 @@ import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum._RumInternalProxy import com.datadog.android.rum.configuration.VitalsUpdateFrequency +import com.datadog.android.rum.metric.networksettled.TimeBasedInitialResourceIdentifier import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy @@ -31,6 +32,7 @@ import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.modules.core.DeviceEventManagerModule import com.google.gson.Gson import java.util.Locale +import kotlin.time.Duration.Companion.seconds /** * Initializes the Android Datadog SDK. @@ -215,6 +217,11 @@ class DdSdkNativeInitialization internal constructor( } ) + configuration.initialResourceThreshold?.let { + val milliseconds = it.seconds.inWholeMilliseconds + configBuilder.setInitialResourceIdentifier(TimeBasedInitialResourceIdentifier(milliseconds)) + } + return configBuilder.build() } diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt index 7e13fb1bc..7a63c5b24 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkNativeInitializationTest.kt @@ -121,6 +121,7 @@ internal class DdSdkNativeInitializationTest { assertThat(configuration.firstPartyHosts?.get("example.com").toString()).isEqualTo( "[B3MULTI, TRACECONTEXT]" ) + assertThat(configuration.initialResourceThreshold).isEqualTo(0.5) } @Test @@ -161,6 +162,7 @@ internal class DdSdkNativeInitializationTest { assertThat(configuration.serviceName).isNull() assertThat(configuration.proxyConfig).isNull() assertThat(configuration.firstPartyHosts).isNull() + assertThat(configuration.initialResourceThreshold).isEqualTo(0.1) } @Test diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt index 407487709..bd9e4a672 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdSdkTest.kt @@ -22,6 +22,7 @@ import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum._RumInternalProxy import com.datadog.android.rum.configuration.VitalsUpdateFrequency +import com.datadog.android.rum.metric.networksettled.TimeBasedInitialResourceIdentifier import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.model.ResourceEvent import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy @@ -42,6 +43,7 @@ import com.facebook.react.bridge.ReadableMap import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.AdvancedForgery import fr.xgouchet.elmyr.annotation.BoolForgery +import fr.xgouchet.elmyr.annotation.DoubleForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -52,6 +54,7 @@ import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import java.util.Locale import java.util.stream.Stream +import kotlin.time.Duration.Companion.seconds import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach @@ -1785,6 +1788,80 @@ internal class DdSdkTest { // endregion + // region initial resource threshold TNS + + @Test + fun `𝕄 initialize W initialize() { initialResourceThreshold is null }`( + @Forgery configuration: DdSdkConfiguration + ) { + // Given + val sdkConfigCaptor = argumentCaptor() + val rumConfigCaptor = argumentCaptor() + val logsConfigCaptor = argumentCaptor() + val traceConfigCaptor = argumentCaptor() + val defaultTimeBasedIdentifier = TimeBasedInitialResourceIdentifier(100) + + // When + testedBridgeSdk.initialize(configuration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + verify(mockDatadog).enableRum(rumConfigCaptor.capture()) + verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) + verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) + } + + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("initialResourceIdentifier", defaultTimeBasedIdentifier) + } + } + + @Test + fun `𝕄 initialize W initialize() { initialResourceThreshold is not null}`( + @DoubleForgery(min = 0.1, max = 5.0) thresholdInSeconds: Double, + @Forgery configuration: DdSdkConfiguration + ) { + // Given + val bridgeConfiguration = configuration.copy( + initialResourceThreshold = thresholdInSeconds + ) + val sdkConfigCaptor = argumentCaptor() + val rumConfigCaptor = argumentCaptor() + val logsConfigCaptor = argumentCaptor() + val traceConfigCaptor = argumentCaptor() + val timeBasedIdentifier = TimeBasedInitialResourceIdentifier( + thresholdInSeconds.seconds.inWholeMilliseconds + ) + + // When + testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise) + + // Then + inOrder(mockDatadog) { + verify(mockDatadog).initialize( + same(mockContext), + sdkConfigCaptor.capture(), + any() + ) + verify(mockDatadog).enableRum(rumConfigCaptor.capture()) + verify(mockDatadog).enableTrace(traceConfigCaptor.capture()) + verify(mockDatadog).enableLogs(logsConfigCaptor.capture()) + } + + assertThat(rumConfigCaptor.firstValue) + .hasField("featureConfiguration") { + it.hasFieldEqualTo("initialResourceIdentifier", timeBasedIdentifier) + } + } + + // endregion + // region version suffix @Test diff --git a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/DdSdkConfigurationExt.kt b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/DdSdkConfigurationExt.kt index b3958b1a5..e1208bbc5 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/DdSdkConfigurationExt.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/DdSdkConfigurationExt.kt @@ -108,6 +108,12 @@ fun DdSdkConfiguration.toReadableJavaOnlyMap(): ReadableMap { map["batchProcessingLevel"] = BatchProcessingLevel.MEDIUM.toString() } + if (initialResourceThreshold != null) { + map.put("initialResourceThreshold", initialResourceThreshold) + } else { + map.put("initialResourceThreshold", 0.1f) + } + return map.toReadableMap() } diff --git a/packages/core/android/src/test/resources/input/complete-configuration.json b/packages/core/android/src/test/resources/input/complete-configuration.json index e77343d74..9c9fcaa7a 100644 --- a/packages/core/android/src/test/resources/input/complete-configuration.json +++ b/packages/core/android/src/test/resources/input/complete-configuration.json @@ -47,6 +47,7 @@ "trackFrustrations": false, "uploadFrequency": "FREQUENT", "version": "2.3.1", - "vitalsUpdateFrequency": "NEVER" + "vitalsUpdateFrequency": "NEVER", + "initialResourceThreshold": 0.5 } } diff --git a/packages/core/datadog-configuration.schema.json b/packages/core/datadog-configuration.schema.json index e7ee954ff..184603b02 100644 --- a/packages/core/datadog-configuration.schema.json +++ b/packages/core/datadog-configuration.schema.json @@ -241,6 +241,10 @@ "trackNonFatalAnrs": { "description": "Enables tracking of non-fatal ANRs on Android.", "type": "boolean" + }, + "initialResourceThreshold" : { + "description": "The amount of time after a view starts where a Resource should be considered when calculating Time to Network-Settled (TNS)", + "type": "number" } }, "required": [ diff --git a/packages/core/ios/Sources/DdSdkConfiguration.swift b/packages/core/ios/Sources/DdSdkConfiguration.swift index ec1eb227d..7a76cf39d 100644 --- a/packages/core/ios/Sources/DdSdkConfiguration.swift +++ b/packages/core/ios/Sources/DdSdkConfiguration.swift @@ -41,6 +41,7 @@ import DatadogRUM - appHangThreshold: The threshold for non-fatal app hangs reporting in seconds. - trackWatchdogTerminations: Whether the SDK should track application termination by the watchdog - batchProcessingLevel: Maximum number of batches processed sequentially without a delay + - initialResourceThreshold: The amount of time after a view starts where a Resource should be considered when calculating Time to Network-Settled (TNS) */ @objc(DdSdkConfiguration) public class DdSdkConfiguration: NSObject { @@ -74,6 +75,7 @@ public class DdSdkConfiguration: NSObject { public var appHangThreshold: Double? = nil public var trackWatchdogTerminations: Bool public var batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel + public var initialResourceThreshold: Double? = nil public init( clientToken: String, @@ -105,7 +107,8 @@ public class DdSdkConfiguration: NSObject { bundleLogsWithTraces: Bool, appHangThreshold: Double?, trackWatchdogTerminations: Bool, - batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel + batchProcessingLevel: Datadog.Configuration.BatchProcessingLevel, + initialResourceThreshold: Double? ) { self.clientToken = clientToken self.env = env @@ -137,6 +140,7 @@ public class DdSdkConfiguration: NSObject { self.appHangThreshold = appHangThreshold self.trackWatchdogTerminations = trackWatchdogTerminations self.batchProcessingLevel = batchProcessingLevel + self.initialResourceThreshold = initialResourceThreshold } } diff --git a/packages/core/ios/Sources/DdSdkNativeInitialization.swift b/packages/core/ios/Sources/DdSdkNativeInitialization.swift index 8d6c2f1df..b6534d04a 100644 --- a/packages/core/ios/Sources/DdSdkNativeInitialization.swift +++ b/packages/core/ios/Sources/DdSdkNativeInitialization.swift @@ -155,6 +155,11 @@ public class DdSdkNativeInitialization: NSObject { eventEmitter?.sendEvent(withName: "RumSessionStarted", body: body) } + var networkSettledResourcePredicate: TimeBasedTNSResourcePredicate? = nil + if let initialResourceThreshold = configuration.initialResourceThreshold as TimeInterval? { + networkSettledResourcePredicate = TimeBasedTNSResourcePredicate(threshold: initialResourceThreshold) + } + return RUM.Configuration( applicationID: configuration.applicationId, sessionSampleRate: (configuration.sampleRate as? NSNumber)?.floatValue ?? Float(DefaultConfiguration.sessionSamplingRate), @@ -167,6 +172,7 @@ public class DdSdkNativeInitialization: NSObject { appHangThreshold: configuration.appHangThreshold, trackWatchdogTerminations: configuration.trackWatchdogTerminations, vitalsUpdateFrequency: configuration.vitalsUpdateFrequency, + networkSettledResourcePredicate: networkSettledResourcePredicate ?? TimeBasedTNSResourcePredicate(), resourceEventMapper: { resourceEvent in if resourceEvent.context?.contextInfo[InternalConfigurationAttributes.dropResource] != nil { return nil diff --git a/packages/core/ios/Sources/RNDdSdkConfiguration.swift b/packages/core/ios/Sources/RNDdSdkConfiguration.swift index c0dc6c6b2..9ed2d8fb6 100644 --- a/packages/core/ios/Sources/RNDdSdkConfiguration.swift +++ b/packages/core/ios/Sources/RNDdSdkConfiguration.swift @@ -42,6 +42,7 @@ extension NSDictionary { let appHangThreshold = object(forKey: "appHangThreshold") as? Double let trackWatchdogTerminations = object(forKey: "trackWatchdogTerminations") as? Bool let batchProcessingLevel = object(forKey: "batchProcessingLevel") as? NSString + let initialResourceThreshold = object(forKey: "initialResourceThreshold") as? Double return DdSdkConfiguration( clientToken: (clientToken != nil) ? clientToken! : String(), @@ -73,7 +74,8 @@ extension NSDictionary { bundleLogsWithTraces: bundleLogsWithTraces ?? DefaultConfiguration.bundleLogsWithTraces, appHangThreshold: appHangThreshold, trackWatchdogTerminations: trackWatchdogTerminations ?? DefaultConfiguration.trackWatchdogTerminations, - batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel() + batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel(), + initialResourceThreshold: initialResourceThreshold ) } @@ -241,7 +243,8 @@ extension Dictionary where Key == String, Value == AnyObject { let appHangThreshold = configuration["appHangThreshold"] as? Double let trackWatchdogTerminations = configuration["trackWatchdogTerminations"] as? Bool let batchProcessingLevel = configuration["batchProcessingLevel"] as? NSString - + let initialResourceThreshold = configuration["initialResourceThreshold"] as? Double + return DdSdkConfiguration( clientToken: clientToken ?? String(), env: env ?? String(), @@ -275,7 +278,8 @@ extension Dictionary where Key == String, Value == AnyObject { bundleLogsWithTraces: bundleLogsWithTraces ?? DefaultConfiguration.bundleLogsWithTraces, appHangThreshold: appHangThreshold, trackWatchdogTerminations: trackWatchdogTerminations ?? DefaultConfiguration.trackWatchdogTerminations, - batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel() + batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel(), + initialResourceThreshold: initialResourceThreshold ) } } diff --git a/packages/core/ios/Tests/DdSdkNativeInitializationTests.swift b/packages/core/ios/Tests/DdSdkNativeInitializationTests.swift index e09df5d17..8a390fba1 100644 --- a/packages/core/ios/Tests/DdSdkNativeInitializationTests.swift +++ b/packages/core/ios/Tests/DdSdkNativeInitializationTests.swift @@ -58,6 +58,7 @@ class DdSdkNativeInitializationTests: XCTestCase { XCTAssertEqual(configuration?.proxyConfig?[kCFProxyPasswordKey] as? String, "proxypassword") let expectedFirstPartyHosts: [String: Set]? = ["example.com": [.b3multi, .tracecontext]] XCTAssertEqual(configuration?.firstPartyHosts, expectedFirstPartyHosts) + XCTAssertEqual(configuration?.initialResourceThreshold, 0.5) } func testReturnsConfigurationWithMinimalData() { @@ -93,6 +94,7 @@ class DdSdkNativeInitializationTests: XCTestCase { XCTAssertNil(configuration?.proxyConfig) let expectedFirstPartyHosts: [String: Set]? = [:] XCTAssertEqual(configuration?.firstPartyHosts, expectedFirstPartyHosts) + XCTAssertEqual(configuration?.initialResourceThreshold, nil) } func testPrintsMessageWithIncorrectFile() { diff --git a/packages/core/ios/Tests/DdSdkTests.swift b/packages/core/ios/Tests/DdSdkTests.swift index ca3ea6392..165fed685 100644 --- a/packages/core/ios/Tests/DdSdkTests.swift +++ b/packages/core/ios/Tests/DdSdkTests.swift @@ -4,6 +4,8 @@ * Copyright 2019-2020 Datadog, Inc. */ +import XCTest + @testable import DatadogCore @testable import DatadogCrashReporting @testable import DatadogInternal @@ -11,7 +13,6 @@ @testable import DatadogRUM @testable import DatadogSDKReactNative @testable import DatadogTrace -import XCTest final class DispatchQueueMock: DispatchQueueType { func async(execute work: @escaping @convention(block) () -> Void) { @@ -55,7 +56,8 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) XCTAssertEqual(consoleMessage, "") @@ -65,9 +67,11 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) - XCTAssertEqual(consoleMessage, "Datadog SDK is already initialized, skipping initialization.") + XCTAssertEqual( + consoleMessage, "Datadog SDK is already initialized, skipping initialization.") } func testResolvesPromiseAfterInitializationIsDone() throws { @@ -88,7 +92,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockJSRefreshRateMonitor, RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockPromiseResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockPromiseResolve, + reject: mockReject) waitForExpectations(timeout: 0.5, handler: nil) } @@ -96,7 +102,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationNoUIKitViewsByDefault() { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertNil(ddConfig.uiKitViewsPredicate) } @@ -104,7 +111,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUIKitViewsTrackingDisabled() { let configuration: DdSdkConfiguration = .mockAny(nativeViewTracking: false) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertNil(ddConfig.uiKitViewsPredicate) } @@ -112,7 +120,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUIKitViewsTrackingEnabled() { let configuration: DdSdkConfiguration = .mockAny(nativeViewTracking: true) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertNotNil(ddConfig.uiKitViewsPredicate) } @@ -120,7 +129,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationNoUIKitUserActionsByDefault() { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertNil(ddConfig.uiKitActionsPredicate) } @@ -128,7 +138,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUIKitUserActionsTrackingDisabled() { let configuration: DdSdkConfiguration = .mockAny(nativeInteractionTracking: false) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertNil(ddConfig.uiKitActionsPredicate) } @@ -136,7 +147,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUIKitUserActionsTrackingEnabled() { let configuration: DdSdkConfiguration = .mockAny(nativeInteractionTracking: true) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertNotNil(ddConfig.uiKitActionsPredicate) } @@ -150,7 +162,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertEqual(Datadog.verbosityLevel, CoreLoggerLevel.debug) } @@ -164,7 +178,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertEqual(Datadog.verbosityLevel, CoreLoggerLevel.debug) } @@ -178,7 +194,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertEqual(Datadog.verbosityLevel, CoreLoggerLevel.warn) } @@ -192,7 +210,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertEqual(Datadog.verbosityLevel, CoreLoggerLevel.error) } @@ -206,7 +226,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertNil(Datadog.verbosityLevel) } @@ -220,7 +242,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: validConfiguration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertNil(Datadog.verbosityLevel) } @@ -237,7 +261,8 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: JSRefreshRateMonitor(), RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) XCTAssertNotNil(coreFromCallback) } @@ -247,7 +272,8 @@ class DdSdkTests: XCTestCase { let configuration: DdSdkConfiguration = .mockAny() DatadogSDKWrapper.shared.setCoreInstance(core: core) - DdSdkNativeInitialization().enableFeatures(sdkConfiguration: configuration, eventEmitter: nil) + DdSdkNativeInitialization().enableFeatures( + sdkConfiguration: configuration, eventEmitter: nil) XCTAssertNotNil(core.features[RUMFeature.name]) XCTAssertNotNil(core.features[LogsFeature.name]) @@ -257,7 +283,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationDefaultEndpoint() { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us1) } @@ -265,7 +292,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUSEndpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "US") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us1) } @@ -273,7 +301,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUS1Endpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "US1") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us1) } @@ -281,7 +310,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUS3Endpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "US3") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us3) } @@ -289,7 +319,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUS5Endpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "US5") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us5) } @@ -297,7 +328,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationUS1FEDEndpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "US1_FED") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us1_fed) } @@ -305,7 +337,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationGOVEndpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "GOV") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .us1_fed) } @@ -313,7 +346,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationEUEndpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "EU") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .eu1) } @@ -321,7 +355,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationEU1Endpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "EU1") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .eu1) } @@ -329,15 +364,19 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationAP1Endpoint() { let configuration: DdSdkConfiguration = .mockAny(site: "AP1") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.site, .ap1) } func testBuildConfigurationAdditionalConfig() { - let configuration: DdSdkConfiguration = .mockAny(additionalConfig: ["foo": "test", "bar": 42]) + let configuration: DdSdkConfiguration = .mockAny(additionalConfig: [ + "foo": "test", "bar": 42, + ]) - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) // swiftlint:disable force_cast XCTAssertEqual(ddConfig.additionalConfiguration["foo"] as! String, "test") @@ -348,7 +387,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationWithNilServiceNameByDefault() { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertNil(ddConfig.service) } @@ -356,7 +396,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationWithServiceName() { let configuration: DdSdkConfiguration = .mockAny(serviceName: "com.example.app") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.service, "com.example.app") } @@ -365,7 +406,8 @@ class DdSdkTests: XCTestCase { let core = MockDatadogCore() let configuration: DdSdkConfiguration = .mockAny(nativeCrashReportEnabled: nil) - DdSdkNativeInitialization().enableFeatures(sdkConfiguration: configuration, eventEmitter: nil) + DdSdkNativeInitialization().enableFeatures( + sdkConfiguration: configuration, eventEmitter: nil) XCTAssertNil(core.features[CrashReportingFeature.name]) } @@ -374,7 +416,8 @@ class DdSdkTests: XCTestCase { let core = MockDatadogCore() let configuration: DdSdkConfiguration = .mockAny(nativeCrashReportEnabled: false) - DdSdkNativeInitialization().enableFeatures(sdkConfiguration: configuration, eventEmitter: nil) + DdSdkNativeInitialization().enableFeatures( + sdkConfiguration: configuration, eventEmitter: nil) XCTAssertNil(core.features[CrashReportingFeature.name]) } @@ -384,23 +427,29 @@ class DdSdkTests: XCTestCase { let configuration: DdSdkConfiguration = .mockAny(nativeCrashReportEnabled: true) DatadogSDKWrapper.shared.setCoreInstance(core: core) - DdSdkNativeInitialization().enableFeatures(sdkConfiguration: configuration, eventEmitter: nil) + DdSdkNativeInitialization().enableFeatures( + sdkConfiguration: configuration, eventEmitter: nil) XCTAssertNotNil(core.features[CrashReportingFeature.name]) } func testBuildConfigurationWithVersionSuffix() { - let configuration: DdSdkConfiguration = .mockAny(additionalConfig: ["_dd.version_suffix": ":codepush-3"]) + let configuration: DdSdkConfiguration = .mockAny(additionalConfig: [ + "_dd.version_suffix": ":codepush-3" + ]) - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration, defaultAppVersion: "1.2.3") + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration, defaultAppVersion: "1.2.3") - XCTAssertEqual(ddConfig.additionalConfiguration["_dd.version"] as! String, "1.2.3:codepush-3") + XCTAssertEqual( + ddConfig.additionalConfiguration["_dd.version"] as! String, "1.2.3:codepush-3") } func testBuildConfigurationFrustrationTrackingEnabledByDefault() { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.trackFrustrations, true) } @@ -408,7 +457,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationFrustrationTrackingEnabledExplicitly() { let configuration: DdSdkConfiguration = .mockAny(trackFrustrations: true) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.trackFrustrations, true) } @@ -416,7 +466,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationFrustrationTrackingDisabled() { let configuration: DdSdkConfiguration = .mockAny(trackFrustrations: false) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.trackFrustrations, false) } @@ -429,7 +480,8 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } ) - bridge.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + bridge.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) bridge.setUser( user: NSDictionary( @@ -456,7 +508,7 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) } - + func testSetUserOptionalId() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -465,7 +517,8 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } ) - bridge.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + bridge.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) bridge.setUser( user: NSDictionary( @@ -491,7 +544,7 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) } - + func testSetUserInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -500,7 +553,8 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } ) - bridge.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + bridge.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) bridge.setUserInfo( userInfo: NSDictionary( @@ -513,7 +567,7 @@ class DdSdkTests: XCTestCase { "extra-info-2": "abc", "extra-info-3": true, "extra-info-4": [ - "nested-extra-info-1" : 456, + "nested-extra-info-1": 456 ], ], ] @@ -524,7 +578,7 @@ class DdSdkTests: XCTestCase { let ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() let userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, "id_123") XCTAssertEqual(userInfo.name, "John Doe") XCTAssertEqual(userInfo.email, "john@doe.com") @@ -532,14 +586,16 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] as? DatadogSDKReactNative.AnyEncodable, - let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] { + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) } else { XCTFail("extra-info-4 is not of expected type or value") } } - + func testSetUserInfoOptionalId() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -548,7 +604,8 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } ) - bridge.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + bridge.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) bridge.setUserInfo( userInfo: NSDictionary( @@ -560,7 +617,7 @@ class DdSdkTests: XCTestCase { "extra-info-2": "abc", "extra-info-3": true, "extra-info-4": [ - "nested-extra-info-1" : 456, + "nested-extra-info-1": 456 ], ], ] @@ -571,7 +628,7 @@ class DdSdkTests: XCTestCase { let ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() let userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, nil) XCTAssertEqual(userInfo.name, "John Doe") XCTAssertEqual(userInfo.email, "john@doe.com") @@ -579,14 +636,16 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] as? DatadogSDKReactNative.AnyEncodable, - let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] { + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) } else { XCTFail("extra-info-4 is not of expected type or value") } } - + func testAddUserExtraInfo() throws { let bridge = DdSdkImplementation( mainDispatchQueue: DispatchQueueMock(), @@ -595,8 +654,9 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } ) - bridge.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) - + bridge.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + bridge.setUserInfo( userInfo: NSDictionary( dictionary: [ @@ -604,7 +664,7 @@ class DdSdkTests: XCTestCase { "name": "John Doe", "email": "john@doe.com", "extraInfo": [ - "extra-info-1": 123, + "extra-info-1": 123 ], ] ), @@ -612,12 +672,13 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - bridge.addUserExtraInfo(extraInfo: NSDictionary( + bridge.addUserExtraInfo( + extraInfo: NSDictionary( dictionary: [ "extra-info-2": "abc", "extra-info-3": true, "extra-info-4": [ - "nested-extra-info-1" : 456, + "nested-extra-info-1": 456 ], ] ), @@ -627,7 +688,7 @@ class DdSdkTests: XCTestCase { let ddContext = try XCTUnwrap(CoreRegistry.default as? DatadogCore).contextProvider.read() let userInfo = try XCTUnwrap(ddContext.userInfo) - + XCTAssertEqual(userInfo.id, "id_123") XCTAssertEqual(userInfo.name, "John Doe") XCTAssertEqual(userInfo.email, "john@doe.com") @@ -635,8 +696,10 @@ class DdSdkTests: XCTestCase { XCTAssertEqual(userInfo.extraInfo["extra-info-2"] as? String, "abc") XCTAssertEqual(userInfo.extraInfo["extra-info-3"] as? Bool, true) - if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] as? DatadogSDKReactNative.AnyEncodable, - let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] { + if let extraInfo4Encodable = userInfo.extraInfo["extra-info-4"] + as? DatadogSDKReactNative.AnyEncodable, + let extraInfo4Dict = extraInfo4Encodable.value as? [String: Int] + { XCTAssertEqual(extraInfo4Dict, ["nested-extra-info-1": 456]) } else { XCTFail("extra-info-4 is not of expected type or value") @@ -652,7 +715,8 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { nil } ) - bridge.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + bridge.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) bridge.setAttributes( attributes: NSDictionary( @@ -680,7 +744,8 @@ class DdSdkTests: XCTestCase { func testBuildLongTaskThreshold() { let configuration: DdSdkConfiguration = .mockAny(nativeLongTaskThresholdMs: 2500) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.longTaskThreshold, 2.5) } @@ -688,20 +753,25 @@ class DdSdkTests: XCTestCase { func testBuildNoLongTaskTracking() { let configuration: DdSdkConfiguration = .mockAny(nativeLongTaskThresholdMs: 0) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.longTaskThreshold, nil) } func testFirstPartyHosts() { - let configuration: DdSdkConfiguration = .mockAny(firstPartyHosts: ([ - ["match": "example.com", "propagatorTypes": ["datadog", "b3"]], - ["match": "datadog.com", "propagatorTypes": ["b3multi", "tracecontext"]], - ] as NSArray).asFirstPartyHosts(), resourceTracingSamplingRate: 66) + let configuration: DdSdkConfiguration = .mockAny( + firstPartyHosts: ([ + ["match": "example.com", "propagatorTypes": ["datadog", "b3"]], + ["match": "datadog.com", "propagatorTypes": ["b3multi", "tracecontext"]], + ] as NSArray).asFirstPartyHosts(), resourceTracingSamplingRate: 66) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) - let expectedFirstPartyHosts: [String: Set]? = ["example.com": [.datadog, .b3], "datadog.com": [.b3multi, .tracecontext]] + let expectedFirstPartyHosts: [String: Set]? = [ + "example.com": [.datadog, .b3], "datadog.com": [.b3multi, .tracecontext], + ] var actualFirstPartyHosts: [String: Set]? var actualTracingSamplingRate: Float? var actualTraceContextInjection: TraceContextInjection? @@ -722,7 +792,8 @@ class DdSdkTests: XCTestCase { func testBuildTelemetrySampleRate() { let configuration: DdSdkConfiguration = .mockAny(telemetrySampleRate: 42.0) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.telemetrySampleRate, 42.0) } @@ -738,7 +809,8 @@ class DdSdkTests: XCTestCase { ] as NSDictionary).asProxyConfig() ) - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.proxyConfiguration?["HTTPProxy"] as? String, "host") XCTAssertEqual(ddConfig.proxyConfiguration?["HTTPPort"] as? NSNumber, 99) @@ -749,7 +821,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationAverageVitalsUpdateFrequency() { let configuration: DdSdkConfiguration = .mockAny(vitalsUpdateFrequency: "average") - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.vitalsUpdateFrequency, .average) } @@ -757,7 +830,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationNeverVitalsUpdateFrequency() { let configuration: DdSdkConfiguration = .mockAny(vitalsUpdateFrequency: "never") - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.vitalsUpdateFrequency, nil) } @@ -765,7 +839,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationAverageUploadFrequency() { let configuration: DdSdkConfiguration = .mockAny(uploadFrequency: "AVERAGE") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.uploadFrequency, .average) } @@ -773,7 +848,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationFrequentUploadFrequency() { let configuration: DdSdkConfiguration = .mockAny(uploadFrequency: "FREQUENT") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.uploadFrequency, .frequent) } @@ -781,7 +857,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationRareUploadFrequency() { let configuration: DdSdkConfiguration = .mockAny(uploadFrequency: "RARE") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.uploadFrequency, .rare) } @@ -789,7 +866,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationSmallBatchSize() { let configuration: DdSdkConfiguration = .mockAny(batchSize: "SMALL") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.batchSize, .small) } @@ -797,7 +875,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationMediumBatchSize() { let configuration: DdSdkConfiguration = .mockAny(batchSize: "MEDIUM") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.batchSize, .medium) } @@ -805,7 +884,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationLargeBatchSize() { let configuration: DdSdkConfiguration = .mockAny(batchSize: "LARGE") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.batchSize, .large) } @@ -813,7 +893,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationLowBatchProcessingLevel() { let configuration: DdSdkConfiguration = .mockAny(batchProcessingLevel: "LOW") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.batchProcessingLevel, .low) } @@ -821,7 +902,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationMediumBatchProcessingLevel() { let configuration: DdSdkConfiguration = .mockAny(batchProcessingLevel: "MEDIUM") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.batchProcessingLevel, .medium) } @@ -829,7 +911,8 @@ class DdSdkTests: XCTestCase { func testBuildConfigurationHighBatchProcessingLevel() { let configuration: DdSdkConfiguration = .mockAny(batchProcessingLevel: "HIGH") - let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration(configuration: configuration) + let ddConfig = DdSdkNativeInitialization().buildSDKConfiguration( + configuration: configuration) XCTAssertEqual(ddConfig.batchProcessingLevel, .high) } @@ -844,7 +927,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockRefreshRateMonitor, RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { rumMonitorMock._internalMock } - ).initialize(configuration: .mockAny(longTaskThresholdMs: 0.0), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(longTaskThresholdMs: 0.0), eventEmitter: nil, + resolve: mockResolve, reject: mockReject) XCTAssertTrue(mockRefreshRateMonitor.isStarted) @@ -868,7 +953,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockRefreshRateMonitor, RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { rumMonitorMock._internalMock } - ).initialize(configuration: .mockAny(longTaskThresholdMs: 0.0, vitalsUpdateFrequency: "never"), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(longTaskThresholdMs: 0.0, vitalsUpdateFrequency: "never"), + eventEmitter: nil, resolve: mockResolve, reject: mockReject) XCTAssertFalse(mockRefreshRateMonitor.isStarted) @@ -889,7 +976,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockRefreshRateMonitor, RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { rumMonitorMock._internalMock } - ).initialize(configuration: .mockAny(longTaskThresholdMs: 0.2, vitalsUpdateFrequency: "never"), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(longTaskThresholdMs: 0.2, vitalsUpdateFrequency: "never"), + eventEmitter: nil, resolve: mockResolve, reject: mockReject) XCTAssertTrue(mockRefreshRateMonitor.isStarted) @@ -914,7 +1003,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockRefreshRateMonitor, RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { rumMonitorMock._internalMock } - ).initialize(configuration: .mockAny(longTaskThresholdMs: 200, vitalsUpdateFrequency: "average"), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(longTaskThresholdMs: 200, vitalsUpdateFrequency: "average"), + eventEmitter: nil, resolve: mockResolve, reject: mockReject) XCTAssertTrue(mockRefreshRateMonitor.isStarted) @@ -954,17 +1045,27 @@ class DdSdkTests: XCTestCase { reject: mockReject ) - let logsFeature = try XCTUnwrap(CoreRegistry.default as? DatadogCore).get(feature: LogsFeature.self) - let customLogsEndpoint = try XCTUnwrap(logsFeature?.requestBuilder as? DatadogLogs.RequestBuilder).customIntakeURL + let logsFeature = try XCTUnwrap(CoreRegistry.default as? DatadogCore).get( + feature: LogsFeature.self) + let customLogsEndpoint = try XCTUnwrap( + logsFeature?.requestBuilder as? DatadogLogs.RequestBuilder + ).customIntakeURL XCTAssertEqual(customLogsEndpoint?.absoluteString, "https://logs.example.com/api/v2/logs") - let rumFeature = try XCTUnwrap(CoreRegistry.default as? DatadogCore).get(feature: RUMFeature.self) - let customRumEndpoint = try XCTUnwrap(rumFeature?.requestBuilder as? DatadogRUM.RequestBuilder).customIntakeURL + let rumFeature = try XCTUnwrap(CoreRegistry.default as? DatadogCore).get( + feature: RUMFeature.self) + let customRumEndpoint = try XCTUnwrap( + rumFeature?.requestBuilder as? DatadogRUM.RequestBuilder + ).customIntakeURL XCTAssertEqual(customRumEndpoint?.absoluteString, "https://rum.example.com/api/v2/rum") - let traceFeature = try XCTUnwrap(CoreRegistry.default as? DatadogCore).get(feature: TraceFeature.self) - let customTraceEndpoint = try XCTUnwrap(traceFeature?.requestBuilder as? TracingRequestBuilder).customIntakeURL - XCTAssertEqual(customTraceEndpoint?.absoluteString, "https://trace.example.com/api/v2/spans") + let traceFeature = try XCTUnwrap(CoreRegistry.default as? DatadogCore).get( + feature: TraceFeature.self) + let customTraceEndpoint = try XCTUnwrap( + traceFeature?.requestBuilder as? TracingRequestBuilder + ).customIntakeURL + XCTAssertEqual( + customTraceEndpoint?.absoluteString, "https://trace.example.com/api/v2/spans") } func testSDKInitializationWithLoggerConfiguration() { @@ -980,7 +1081,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: MockJSRefreshRateMonitor(), RUMMonitorProvider: { rumMonitorMock }, RUMMonitorInternalProvider: { rumMonitorMock._internalMock } - ).initialize(configuration: configuration, eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: configuration, eventEmitter: nil, resolve: mockResolve, + reject: mockReject) XCTAssertFalse(DatadogSDKWrapper.shared.loggerConfiguration.bundleWithRumEnabled) XCTAssertFalse(DatadogSDKWrapper.shared.loggerConfiguration.bundleWithTraceEnabled) @@ -989,7 +1092,8 @@ class DdSdkTests: XCTestCase { func testBackgroundTrackingEnabled() { let configuration: DdSdkConfiguration = .mockAny(trackBackgroundEvents: true) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.trackBackgroundEvents, true) } @@ -997,7 +1101,8 @@ class DdSdkTests: XCTestCase { func testBackgroundTrackingDisabled() { let configuration: DdSdkConfiguration = .mockAny(trackBackgroundEvents: false) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.trackBackgroundEvents, false) } @@ -1005,7 +1110,8 @@ class DdSdkTests: XCTestCase { func testBackgroundTrackingUndefined() { let configuration: DdSdkConfiguration = .mockAny(trackBackgroundEvents: nil) - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) XCTAssertEqual(ddConfig.trackBackgroundEvents, false) } @@ -1016,7 +1122,11 @@ class DdSdkTests: XCTestCase { nativeCrashReportEnabled: false, nativeLongTaskThresholdMs: 0.0, longTaskThresholdMs: 0.1, - configurationForTelemetry: ["initializationType": "LEGACY", "trackErrors": true, "trackInteractions": true, "trackNetworkRequests": true, "reactVersion": "18.2.0", "reactNativeVersion": "0.71.0"] + configurationForTelemetry: [ + "initializationType": "LEGACY", "trackErrors": true, "trackInteractions": true, + "trackNetworkRequests": true, "reactVersion": "18.2.0", + "reactNativeVersion": "0.71.0", + ] ) DatadogSDKWrapper.shared.setCoreInstance(core: core) @@ -1036,7 +1146,8 @@ class DdSdkTests: XCTestCase { func testDropsResourceMarkedAsDropped() throws { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) let resourceEventMapper = try XCTUnwrap(ddConfig.resourceEventMapper) @@ -1052,7 +1163,8 @@ class DdSdkTests: XCTestCase { func testDropsActionMarkedAsDropped() throws { let configuration: DdSdkConfiguration = .mockAny() - let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration(configuration: configuration, eventEmitter: nil) + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) let actionEventMapper = try XCTUnwrap(ddConfig.actionEventMapper) @@ -1075,7 +1187,9 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockJSRefreshRateMonitor, RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: .mockAny(longTaskThresholdMs: 0.2), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(longTaskThresholdMs: 0.2), eventEmitter: nil, + resolve: mockResolve, reject: mockReject) XCTAssertTrue(bridge.isSameQueue(queue: mockJSRefreshRateMonitor.jsQueue!)) } @@ -1092,7 +1206,8 @@ class DdSdkTests: XCTestCase { jsRefreshRateMonitor: mockJSRefreshRateMonitor, RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } - ).initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + ).initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) XCTAssertNotNil(mockListener.core) } @@ -1102,13 +1217,47 @@ class DdSdkTests: XCTestCase { let core = MockDatadogCore() DatadogSDKWrapper.shared.setCoreInstance(core: core) - DdSdkNativeInitialization().enableFeatures(sdkConfiguration: configuration, eventEmitter: nil) + DdSdkNativeInitialization().enableFeatures( + sdkConfiguration: configuration, eventEmitter: nil) - DdSdkImplementation().consumeWebviewEvent(message: "{\"eventType\":\"rum\",\"event\":{\"blabla\":\"custom message\"}}", resolve: mockResolve, reject: mockReject) + DdSdkImplementation().consumeWebviewEvent( + message: "{\"eventType\":\"rum\",\"event\":{\"blabla\":\"custom message\"}}", + resolve: mockResolve, reject: mockReject) XCTAssertNotNil(core.baggages["browser-rum-event"]) } + func testInitialResourceThreshold() { + let configuration: DdSdkConfiguration = .mockAny(initialResourceThreshold: 0.5) + + let ddConfig = DdSdkNativeInitialization().buildRUMConfiguration( + configuration: configuration, eventEmitter: nil) + + let predicate = ddConfig.networkSettledResourcePredicate + let earlyResource = TNSResourceParams( + url: "https://datadoghq.com", + timeSinceViewStart: 0.1, + viewName: "Home" + ) + + let onThresholdLimitResource = TNSResourceParams( + url: "https://datadoghq.com", + timeSinceViewStart: 0.5, + viewName: "Home" + ) + + let lateResource = TNSResourceParams( + url: "https://datadoghq.com", + timeSinceViewStart: 0.6, + viewName: "Home" + ) + + XCTAssertTrue(predicate is TimeBasedTNSResourcePredicate) + XCTAssertTrue(predicate.isInitialResource(from: earlyResource)) + XCTAssertTrue(predicate.isInitialResource(from: onThresholdLimitResource)) + XCTAssertFalse(predicate.isInitialResource(from: lateResource)) + } + func testClearAllData() throws { // Given let bridge = DispatchQueueMock() @@ -1121,7 +1270,8 @@ class DdSdkTests: XCTestCase { RUMMonitorProvider: { MockRUMMonitor() }, RUMMonitorInternalProvider: { nil } ) - sdk.initialize(configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) + sdk.initialize( + configuration: .mockAny(), eventEmitter: nil, resolve: mockResolve, reject: mockReject) let core = try XCTUnwrap(CoreRegistry.default as? DatadogCore) // On SDK init, underlying `ConsentAwareDataWriter` performs data migration for each feature, which includes @@ -1134,11 +1284,17 @@ class DdSdkTests: XCTestCase { core.directory.getFeatureDirectories(forFeatureNamed: "tracing"), ] - let allDirectories: [Directory] = featureDirectories.flatMap { [$0.authorized, $0.unauthorized] } - try allDirectories.forEach { directory in _ = try directory.createFile(named: .mockRandom()) } + let allDirectories: [Directory] = featureDirectories.flatMap { + [$0.authorized, $0.unauthorized] + } + try allDirectories.forEach { directory in _ = try directory.createFile(named: .mockRandom()) + } - let numberOfFiles = try allDirectories.reduce(0) { acc, nextDirectory in try acc + nextDirectory.files().count } - XCTAssertEqual(numberOfFiles, 4, "Each feature stores 2 files - one authorised and one unauthorised") + let numberOfFiles = try allDirectories.reduce(0) { acc, nextDirectory in + try acc + nextDirectory.files().count + } + XCTAssertEqual( + numberOfFiles, 4, "Each feature stores 2 files - one authorised and one unauthorised") // When sdk.clearAllData(resolve: mockResolve, reject: mockReject) @@ -1147,7 +1303,9 @@ class DdSdkTests: XCTestCase { core.readWriteQueue.sync {} // Then - let newNumberOfFiles = try allDirectories.reduce(0) { acc, nextDirectory in try acc + nextDirectory.files().count } + let newNumberOfFiles = try allDirectories.reduce(0) { acc, nextDirectory in + try acc + nextDirectory.files().count + } XCTAssertEqual(newNumberOfFiles, 0, "All files must be removed") } } @@ -1160,7 +1318,9 @@ private final class MockJSRefreshRateMonitor: RefreshRateMonitor { init() {} - public func startMonitoring(jsQueue: DispatchQueueType, frameTimeCallback: @escaping frame_time_callback) { + public func startMonitoring( + jsQueue: DispatchQueueType, frameTimeCallback: @escaping frame_time_callback + ) { self.frameTimeCallback = frameTimeCallback self.jsQueue = jsQueue isStarted = true @@ -1202,7 +1362,8 @@ extension DdSdkConfiguration { bundleLogsWithTraces: Bool = true, appHangThreshold: Double? = nil, trackWatchdogTerminations: Bool = false, - batchProcessingLevel: NSString? = "MEDIUM" + batchProcessingLevel: NSString? = "MEDIUM", + initialResourceThreshold: Double? = nil ) -> DdSdkConfiguration { DdSdkConfiguration( clientToken: clientToken as String, @@ -1234,7 +1395,8 @@ extension DdSdkConfiguration { bundleLogsWithTraces: bundleLogsWithTraces, appHangThreshold: appHangThreshold, trackWatchdogTerminations: trackWatchdogTerminations, - batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel() + batchProcessingLevel: batchProcessingLevel.asBatchProcessingLevel(), + initialResourceThreshold: initialResourceThreshold ) } } diff --git a/packages/core/ios/Tests/Fixtures/complete-configuration.json b/packages/core/ios/Tests/Fixtures/complete-configuration.json index e77343d74..9c9fcaa7a 100644 --- a/packages/core/ios/Tests/Fixtures/complete-configuration.json +++ b/packages/core/ios/Tests/Fixtures/complete-configuration.json @@ -47,6 +47,7 @@ "trackFrustrations": false, "uploadFrequency": "FREQUENT", "version": "2.3.1", - "vitalsUpdateFrequency": "NEVER" + "vitalsUpdateFrequency": "NEVER", + "initialResourceThreshold": 0.5 } } diff --git a/packages/core/src/DdSdkReactNative.tsx b/packages/core/src/DdSdkReactNative.tsx index 78ca564df..a1f356ba7 100644 --- a/packages/core/src/DdSdkReactNative.tsx +++ b/packages/core/src/DdSdkReactNative.tsx @@ -343,7 +343,8 @@ export class DdSdkReactNative { configuration.appHangThreshold, configuration.resourceTracingSamplingRate, configuration.trackWatchdogTerminations, - configuration.batchProcessingLevel + configuration.batchProcessingLevel, + configuration.initialResourceThreshold ); }; diff --git a/packages/core/src/DdSdkReactNativeConfiguration.tsx b/packages/core/src/DdSdkReactNativeConfiguration.tsx index ff24eef76..158d258eb 100644 --- a/packages/core/src/DdSdkReactNativeConfiguration.tsx +++ b/packages/core/src/DdSdkReactNativeConfiguration.tsx @@ -314,6 +314,14 @@ export class DdSdkReactNativeConfiguration { */ public appHangThreshold?: number; + /** + * The amount of time after a view starts where a Resource should be + * considered when calculating Time to Network-Settled (TNS). TNS will be + * calculated using all resources that start withing the specified threshold, in seconds. + * Defaults to 0.1 seconds. + */ + public initialResourceThreshold?: number; + /** * Determines whether the SDK should track application termination by the watchdog on iOS. Default: `false`. */ @@ -460,6 +468,7 @@ export type PartialInitializationConfiguration = { readonly bundleLogsWithRum?: boolean; readonly bundleLogsWithTraces?: boolean; readonly batchProcessingLevel?: BatchProcessingLevel; + readonly initialResourceThreshold?: number; }; const setConfigurationAttribute = < diff --git a/packages/core/src/__tests__/DdSdkReactNative.test.tsx b/packages/core/src/__tests__/DdSdkReactNative.test.tsx index 987ce322f..f1d5e9a90 100644 --- a/packages/core/src/__tests__/DdSdkReactNative.test.tsx +++ b/packages/core/src/__tests__/DdSdkReactNative.test.tsx @@ -481,6 +481,29 @@ describe('DdSdkReactNative', () => { ddSdkConfiguration.additionalConfiguration['_dd.version_suffix'] ).toBeUndefined(); }); + + it('initializes with initialResourceThreshold when it is specified', async () => { + // GIVEN + const fakeAppId = '1'; + const fakeClientToken = '2'; + const fakeEnvName = 'env'; + const configuration = new DdSdkReactNativeConfiguration( + fakeClientToken, + fakeEnvName, + fakeAppId + ); + configuration.initialResourceThreshold = 0.123; + + // WHEN + await DdSdkReactNative.initialize(configuration); + + // THEN + expect(NativeModules.DdSdk.initialize).toHaveBeenCalledWith( + expect.objectContaining({ + initialResourceThreshold: 0.123 + }) + ); + }); }); describe('feature enablement', () => { diff --git a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts index a1c18af4b..6d9d081af 100644 --- a/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts +++ b/packages/core/src/__tests__/DdSdkReactNativeConfiguration.test.ts @@ -121,7 +121,8 @@ describe('DdSdkReactNativeConfiguration', () => { trace: 'https://trace.example.com/' }, bundleLogsWithRum: true, - bundleLogsWithTraces: true + bundleLogsWithTraces: true, + initialResourceThreshold: 0.123 } ) ).toMatchInlineSnapshot(` @@ -147,6 +148,7 @@ describe('DdSdkReactNativeConfiguration', () => { "firstPartyHosts": [ "api.com", ], + "initialResourceThreshold": 0.123, "logEventMapper": [Function], "longTaskThresholdMs": 567, "nativeCrashReportEnabled": true, diff --git a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx index 5de900544..ff5b4a808 100644 --- a/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx +++ b/packages/core/src/sdk/DatadogProvider/__tests__/initialization.test.tsx @@ -86,6 +86,7 @@ describe('DatadogProvider', () => { "customEndpoints": {}, "env": "fakeEnv", "firstPartyHosts": [], + "initialResourceThreshold": undefined, "longTaskThresholdMs": 0, "nativeCrashReportEnabled": false, "nativeInteractionTracking": false, diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index 7eafcdc35..aaac7af53 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -61,9 +61,10 @@ export class DdSdkConfiguration { readonly bundleLogsWithTraces: boolean, readonly trackNonFatalAnrs: boolean | undefined, readonly appHangThreshold: number | undefined, - readonly resourceTracingSamplingRate: number | undefined, + readonly resourceTracingSamplingRate: number, readonly trackWatchdogTerminations: boolean | undefined, - readonly batchProcessingLevel: BatchProcessingLevel // eslint-disable-next-line no-empty-function + readonly batchProcessingLevel: BatchProcessingLevel, // eslint-disable-next-line no-empty-function + readonly initialResourceThreshold: number | undefined ) {} }