diff --git a/example-new-architecture/App.tsx b/example-new-architecture/App.tsx index 190667007..b56f45d5c 100644 --- a/example-new-architecture/App.tsx +++ b/example-new-architecture/App.tsx @@ -90,6 +90,10 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials'; const spanId = await DdTrace.startSpan('test span'); await DdTrace.finishSpan(spanId); + + setTimeout(async () => { + await DdRum.reportAppFullyDisplayed(); + }, 5000); })(); function AppWithProviders() { diff --git a/example/src/ddUtils.tsx b/example/src/ddUtils.tsx index 190aba093..46ab56a8a 100644 --- a/example/src/ddUtils.tsx +++ b/example/src/ddUtils.tsx @@ -1,6 +1,7 @@ import { DatadogProviderConfiguration, DdLogs, + DdRum, DdSdkReactNative, CoreConfiguration, SdkVerbosity, @@ -61,6 +62,10 @@ export function getDatadogConfig(trackingConsent: TrackingConsent) { DdLogs.info('The RN Sdk was properly initialized') DdSdkReactNative.setUserInfo({id: "1337", name: "Xavier", email: "xg@example.com", extraInfo: { type: "premium" } }) DdSdkReactNative.addAttributes({campaign: "ad-network"}) + + setTimeout(async () => { + await DdRum.reportAppFullyDisplayed(); + }, 5000); } // Legacy SDK Setup @@ -93,6 +98,10 @@ export function initializeDatadog(trackingConsent: TrackingConsent) { DdLogs.info('The RN Sdk was properly initialized') DdSdkReactNative.setUserInfo({id: "1337", name: "Xavier", email: "xg@example.com", extraInfo: { type: "premium" } }) DdSdkReactNative.addAttributes({campaign: "ad-network"}) + + setTimeout(async () => { + await DdRum.reportAppFullyDisplayed(); + }, 5000); }); // Enable the Flags feature. diff --git a/packages/core/__mocks__/react-native.ts b/packages/core/__mocks__/react-native.ts index db10d7f31..18e9a7be1 100644 --- a/packages/core/__mocks__/react-native.ts +++ b/packages/core/__mocks__/react-native.ts @@ -148,6 +148,9 @@ actualRN.NativeModules.DdRum = { addViewLoadingTime: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, + reportAppFullyDisplayed: jest.fn().mockImplementation( + () => new Promise(resolve => resolve()) + ) as jest.MockedFunction, stopSession: jest.fn().mockImplementation( () => new Promise(resolve => resolve()) ) as jest.MockedFunction, diff --git a/packages/core/android/build.gradle b/packages/core/android/build.gradle index 6974c517a..2a714fcea 100644 --- a/packages/core/android/build.gradle +++ b/packages/core/android/build.gradle @@ -227,6 +227,7 @@ dependencies { implementation "com.datadoghq:dd-sdk-android-webview:3.10.0" implementation "com.datadoghq:dd-sdk-android-ndk:3.10.0" implementation "com.datadoghq:dd-sdk-android-flags:3.10.0" + // implementation "com.datadoghq:dd-sdk-android-rum-prelaunch:3.10.0" // TO DO - Add module when bumping dd-sdk-android implementation "com.google.code.gson:gson:2.11.0" testImplementation "org.junit.platform:junit-platform-launcher:1.6.2" testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2" diff --git a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt index 41f525761..b63151ccb 100644 --- a/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt +++ b/packages/core/android/src/main/kotlin/com/datadog/reactnative/DdRumImplementation.kt @@ -309,6 +309,16 @@ class DdRumImplementation(private val datadog: DatadogWrapper = DatadogSDKWrappe promise.resolve(null) } + /** + * This method can be used to mark the moment in time when the UI of the app is considered fully displayed. + * The duration between the application launch and this moment of time will be shown as TTFD (time to full display) + * in the RUM session explorer. Only the first call to this method will have any effect for a given RUM session. + */ + fun reportAppFullyDisplayed(promise: Promise) { + datadog.getRumMonitor().reportAppFullyDisplayed() + promise.resolve(null) + } + /** * Stops the current RUM Session. */ diff --git a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt index e7d80ff25..1bcd3726d 100644 --- a/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt +++ b/packages/core/android/src/newarch/kotlin/com/datadog/reactnative/DdRum.kt @@ -259,6 +259,16 @@ class DdRum( implementation.addViewLoadingTime(overwrite, promise) } + /** + * This method can be used to mark the moment in time when the UI of the app is considered fully displayed. + * The duration between the application launch and this moment of time will be shown as TTFD (time to full display) + * in the RUM session explorer. Only the first call to this method will have any effect for a given RUM session. + */ + @ReactMethod + override fun reportAppFullyDisplayed(promise: Promise) { + implementation.reportAppFullyDisplayed(promise) + } + /** * Stops the current RUM Session. */ diff --git a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt index dc966dce1..99953cbb4 100644 --- a/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt +++ b/packages/core/android/src/oldarch/kotlin/com/datadog/reactnative/DdRum.kt @@ -245,6 +245,16 @@ class DdRum( implementation.addViewLoadingTime(overwrite, promise) } + /** + * This method can be used to mark the moment in time when the UI of the app is considered fully displayed. + * The duration between the application launch and this moment of time will be shown as TTFD (time to full display) + * in the RUM session explorer. Only the first call to this method will have any effect for a given RUM session. + */ + @ReactMethod + fun reportAppFullyDisplayed(promise: Promise) { + implementation.reportAppFullyDisplayed(promise) + } + /** * Stops the current RUM Session. */ diff --git a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt index d5315fa8b..7ec474783 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/reactnative/DdRumTest.kt @@ -531,6 +531,15 @@ internal class DdRumTest { verify(mockRumMonitor).addViewLoadingTime(overwrite) } + @Test + fun `M call reportAppFullyDisplayed w reportAppFullyDisplayed()`() { + // When + testedDdRum.reportAppFullyDisplayed(mockPromise) + + // Then + verify(mockRumMonitor).reportAppFullyDisplayed() + } + @Test fun `M call stopSession W stopSession()`() { // When diff --git a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt index df0e8b5ad..5b52a9733 100644 --- a/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt +++ b/packages/core/android/src/test/kotlin/com/datadog/tools/unit/MockRumMonitor.kt @@ -63,6 +63,9 @@ class MockRumMonitor : RumMonitor { @ExperimentalRumApi override fun addViewLoadingTime(overwrite: Boolean) {} + @ExperimentalRumApi + override fun reportAppFullyDisplayed() {} + override fun getCurrentSessionId(callback: (String?) -> Unit) {} override fun addViewAttributes(attributes: Map) {} @@ -172,7 +175,4 @@ class MockRumMonitor : RumMonitor { failureReason: OperationFailureReason, attributes: Map ) {} - - @ExperimentalRumApi - override fun reportAppFullyDisplayed() {} } diff --git a/packages/core/ios/Sources/DdRum.mm b/packages/core/ios/Sources/DdRum.mm index 58c9938cf..72ecff9c6 100644 --- a/packages/core/ios/Sources/DdRum.mm +++ b/packages/core/ios/Sources/DdRum.mm @@ -145,7 +145,15 @@ @implementation DdRum [self addViewLoadingTime:overwrite resolve:resolve reject:reject]; } -RCT_REMAP_METHOD(stopSession, withResolve:(RCTPromiseResolveBlock)resolve +RCT_EXPORT_METHOD(reportAppFullyDisplayed: + (RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) +{ + [self reportAppFullyDisplayed: resolve reject:reject]; +} + +RCT_EXPORT_METHOD(stopSession: + (RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) { [self stopSession:resolve reject:reject]; @@ -262,6 +270,10 @@ - (void)addViewLoadingTime:(BOOL)overwrite resolve:(RCTPromiseResolveBlock)resol [self.ddRumImplementation addViewLoadingTimeWithOverwrite:overwrite resolve:resolve reject:reject]; } +- (void)reportAppFullyDisplayed:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + [self.ddRumImplementation reportAppFullyDisplayedWithResolve:resolve reject: reject]; +} + - (void)startAction:(NSString *)type name:(NSString *)name context:(NSDictionary *)context timestampMs:(double)timestampMs resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { [self.ddRumImplementation startActionWithType:type name:name context:context timestampMs:timestampMs resolve:resolve reject:reject]; } diff --git a/packages/core/ios/Sources/DdRumImplementation.swift b/packages/core/ios/Sources/DdRumImplementation.swift index fec42b407..f37b51eb6 100644 --- a/packages/core/ios/Sources/DdRumImplementation.swift +++ b/packages/core/ios/Sources/DdRumImplementation.swift @@ -262,6 +262,12 @@ public class DdRumImplementation: NSObject { resolve(nil) } + @objc + public func reportAppFullyDisplayed(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { + nativeRUM.reportAppFullyDisplayed() + resolve(nil) + } + @objc public func stopSession(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void { nativeRUM.stopSession() diff --git a/packages/core/ios/Tests/DdRumTests.swift b/packages/core/ios/Tests/DdRumTests.swift index 0ede8766d..58f0a2d82 100644 --- a/packages/core/ios/Tests/DdRumTests.swift +++ b/packages/core/ios/Tests/DdRumTests.swift @@ -466,6 +466,14 @@ internal class DdRumTests: XCTestCase { XCTAssertEqual(mockNativeRUM.receivedAttributes.count, 0) } + func testReportAppFullyDisplayed() throws { + rum.reportAppFullyDisplayed(resolve: mockResolve, reject: mockReject) + + XCTAssertEqual(mockNativeRUM.calledMethods.count, 1) + XCTAssertEqual(mockNativeRUM.calledMethods.last, .reportAppFullyDisplayed()) + XCTAssertEqual(mockNativeRUM.receivedAttributes.count, 0) + } + func testStopSession() throws { rum.stopSession(resolve: mockResolve, reject: mockReject) diff --git a/packages/core/ios/Tests/MockRUMMonitor.swift b/packages/core/ios/Tests/MockRUMMonitor.swift index ce1d2c6cd..0b7fc02bc 100644 --- a/packages/core/ios/Tests/MockRUMMonitor.swift +++ b/packages/core/ios/Tests/MockRUMMonitor.swift @@ -10,10 +10,6 @@ @testable import DatadogSDKReactNative internal class MockRUMMonitor: RUMMonitorProtocol { - func reportAppFullyDisplayed() { - // not implemented - } - func currentSessionID(completion: @escaping (String?) -> Void) { // not implemented } @@ -69,6 +65,7 @@ internal class MockRUMMonitor: RUMMonitorProtocol { case addViewAttributes(_: Int? = nil) // We need an attribute for the case to be Equatable case removeViewAttributes(keys: [String]) case addViewLoadingTime(overwrite: Bool) + case reportAppFullyDisplayed(_: Int? = nil) // We need an attribute for the case to be Equatable case stopSession(_: Int? = nil) // We need an attribute for the case to be Equatable case addResourceMetrics(resourceKey: String, fetch: Interval, @@ -151,6 +148,9 @@ internal class MockRUMMonitor: RUMMonitorProtocol { func addViewLoadingTime(overwrite: Bool) { calledMethods.append(.addViewLoadingTime(overwrite: overwrite)) } + func reportAppFullyDisplayed() { + calledMethods.append(.reportAppFullyDisplayed()) + } func stopSession() { calledMethods.append(.stopSession()) } diff --git a/packages/core/jest/mock.js b/packages/core/jest/mock.js index aadc79d27..4d91a73c4 100644 --- a/packages/core/jest/mock.js +++ b/packages/core/jest/mock.js @@ -143,6 +143,9 @@ module.exports = { addViewLoadingTime: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), + reportAppFullyDisplayed: jest + .fn() + .mockImplementation(() => new Promise(resolve => resolve())), addFeatureFlagEvaluation: jest .fn() .mockImplementation(() => new Promise(resolve => resolve())), diff --git a/packages/core/src/rum/DdRum.ts b/packages/core/src/rum/DdRum.ts index 9a7f15dae..7a3aecb9c 100644 --- a/packages/core/src/rum/DdRum.ts +++ b/packages/core/src/rum/DdRum.ts @@ -427,6 +427,16 @@ class DdRumWrapper implements DdRumType { ); }; + reportAppFullyDisplayed = (): Promise => { + InternalLog.log( + 'Reporting App Fully Displayed (TTFD)', + SdkVerbosity.DEBUG + ); + return bufferVoidNativeCall(() => + this.nativeRum.reportAppFullyDisplayed() + ); + }; + stopSession = (): Promise => { InternalLog.log('Stopping RUM Session', SdkVerbosity.DEBUG); clearCachedSessionId(); diff --git a/packages/core/src/rum/__tests__/DdRum.test.ts b/packages/core/src/rum/__tests__/DdRum.test.ts index 7d0a3688b..20c319f94 100644 --- a/packages/core/src/rum/__tests__/DdRum.test.ts +++ b/packages/core/src/rum/__tests__/DdRum.test.ts @@ -1884,6 +1884,16 @@ describe('DdRum', () => { }); }); + describe('DdRum.reportAppFullyDisplayed', () => { + it('calls the native API', async () => { + await DdRum.reportAppFullyDisplayed(); + + expect( + NativeModules.DdRum.reportAppFullyDisplayed + ).toHaveBeenCalledTimes(1); + }); + }); + describe('PropagatorTypes', () => { it('matches with the native name of propagators', () => { /** diff --git a/packages/core/src/rum/types.ts b/packages/core/src/rum/types.ts index b879010f7..8b173376a 100644 --- a/packages/core/src/rum/types.ts +++ b/packages/core/src/rum/types.ts @@ -181,6 +181,13 @@ export type DdRumType = { */ addViewLoadingTime(overwrite: boolean): Promise; + /** + * This method can be used to mark the moment in time when the UI of the app is considered fully displayed. + * The duration between the application launch and this moment of time will be shown as TTFD (time to full display) + * in the RUM session explorer. Only the first call to this method will have any effect for a given RUM session. + */ + reportAppFullyDisplayed(): Promise; + /** * Stops the current RUM Session. */ diff --git a/packages/core/src/specs/NativeDdRum.ts b/packages/core/src/specs/NativeDdRum.ts index dbe417f44..00950807a 100644 --- a/packages/core/src/specs/NativeDdRum.ts +++ b/packages/core/src/specs/NativeDdRum.ts @@ -170,6 +170,13 @@ export interface Spec extends TurboModule { */ addViewLoadingTime(overwrite: boolean): Promise; + /** + * This method can be used to mark the moment in time when the UI of the app is considered fully displayed. + * The duration between the application launch and this moment of time will be shown as TTFD (time to full display) + * in the RUM session explorer. Only the first call to this method will have any effect for a given RUM session. + */ + reportAppFullyDisplayed(): Promise; + /** * Stops the current RUM Session. */