Native set load measured (#8242)

* Track notification loaded on native side instead of JS

* iOS details and iOS 18 changes

* Fix tests
This commit is contained in:
Daniel Espino García 2024-10-14 16:07:06 +02:00 committed by GitHub
parent e7d597f70e
commit bc8a42b161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 89 additions and 4 deletions

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNUtils from '@mattermost/rnutils';
import performance from 'react-native-performance';
import {mockApiClient} from '@test/mock_api_client';
@ -64,6 +65,12 @@ describe('load metrics', () => {
'setInterval',
]}).setSystemTime(new Date(TEST_EPOCH));
PerformanceMetricsManager = new PerformanceMetricsManagerClass();
const mockHasRegisteredLoad = {hasRegisteredLoad: false};
jest.mocked(RNUtils.setHasRegisteredLoad).mockImplementation(() => {
mockHasRegisteredLoad.hasRegisteredLoad = true;
});
jest.mocked(RNUtils.getHasRegisteredLoad).mockImplementation(() => mockHasRegisteredLoad);
});
afterEach(async () => {
jest.useRealTimers();
@ -91,6 +98,26 @@ describe('load metrics', () => {
expect(mockApiClient.post).toHaveBeenCalledWith(expectedUrl, expectedRequest);
});
it('only register load once', async () => {
performance.mark('nativeLaunchStart');
const measure = getMeasure(TEST_EPOCH, 0);
const expectedRequest = getBaseReportRequest(measure.timestamp, measure.timestamp + 1);
expectedRequest.body.histograms = [measure];
PerformanceMetricsManager.setLoadTarget('HOME');
PerformanceMetricsManager.finishLoad('HOME', serverUrl);
await TestHelper.tick();
jest.advanceTimersByTime(INTERVAL_TIME);
await TestHelper.tick();
expect(mockApiClient.post).toHaveBeenCalledWith(expectedUrl, expectedRequest);
mockApiClient.post.mockClear();
PerformanceMetricsManager.finishLoad('HOME', serverUrl);
await TestHelper.tick();
jest.advanceTimersByTime(INTERVAL_TIME);
await TestHelper.tick();
expect(mockApiClient.post).not.toHaveBeenCalled();
});
it('retry if the mark is not yet present', async () => {
const measure = getMeasure(TEST_EPOCH + (RETRY_TIME * 2), RETRY_TIME);
const expectedRequest = getBaseReportRequest(measure.timestamp, measure.timestamp + 1);

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import RNUtils from '@mattermost/rnutils';
import {AppState, type AppStateStatus} from 'react-native';
import performance from 'react-native-performance';
@ -18,7 +19,6 @@ const MAX_RETRIES = 3;
class PerformanceMetricsManager {
private target: Target;
private batchers: {[serverUrl: string]: Batcher} = {};
private hasRegisteredLoad = false;
private lastAppStateIsActive = AppState.currentState === 'active';
constructor() {
@ -49,7 +49,7 @@ class PerformanceMetricsManager {
}
public skipLoadMetric() {
this.hasRegisteredLoad = true;
RNUtils.setHasRegisteredLoad();
}
public finishLoad(location: Target, serverUrl: string) {
@ -57,7 +57,7 @@ class PerformanceMetricsManager {
}
private finishLoadWithRetries(location: Target, serverUrl: string, retries: number) {
if (this.target !== location || this.hasRegisteredLoad) {
if (this.target !== location || RNUtils.getHasRegisteredLoad().hasRegisteredLoad) {
return;
}
@ -79,7 +79,7 @@ class PerformanceMetricsManager {
logWarning('We could not retrieve the mobile load metric');
}
this.hasRegisteredLoad = true;
RNUtils.setHasRegisteredLoad();
}
public startMetric(metricName: MetricName) {

View file

@ -17,6 +17,7 @@ class RNUtilsModuleImpl(private val reactContext: ReactApplicationContext) {
const val NAME = "RNUtils"
private var context: ReactApplicationContext? = null
private var hasRegisteredLoad = false
fun sendJSEvent(eventName: String, data: ReadableMap?) {
if (context?.hasActiveReactInstance() == true) {
@ -73,6 +74,16 @@ class RNUtilsModuleImpl(private val reactContext: ReactApplicationContext) {
return SplitView.getWindowDimensions()
}
fun setHasRegisteredLoad() {
hasRegisteredLoad = true
}
fun getHasRegisteredLoad(): WritableMap {
val map = Arguments.createMap()
map.putBoolean("hasRegisteredLoad", hasRegisteredLoad)
return map
}
fun unlockOrientation() {}
fun lockPortrait() {}

View file

@ -29,6 +29,9 @@ class RNUtilsModule(val reactContext: ReactApplicationContext) : NativeRNUtilsSp
override fun getWindowDimensions(): WritableMap? = implementation.getWindowDimensions()
override fun setHasRegisteredLoad() = implementation.setHasRegisteredLoad()
override fun getHasRegisteredLoad(): WritableMap = implementation.getHasRegisteredLoad()
override fun unlockOrientation() {
implementation.unlockOrientation()
}

View file

@ -44,6 +44,16 @@ class RNUtilsModule(context: ReactApplicationContext) :
return implementation.getWindowDimensions()
}
@ReactMethod
fun setHasRegisteredLoad() {
implementation.setHasRegisteredLoad()
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun getHasRegisteredLoad(): WritableMap {
return implementation.getHasRegisteredLoad()
}
@ReactMethod
fun unlockOrientation() {
implementation.unlockOrientation()

View file

@ -76,6 +76,14 @@ RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(getWindowDimensions, NSDictionary*, window
return [wrapper getWindowDimensions];
}
RCT_REMAP_BLOCKING_SYNCHRONOUS_METHOD(getHasRegisteredLoad, NSDictionary*, getLoad) {
return [wrapper getHasRegisteredLoad];
}
RCT_REMAP_METHOD(setHasRegisteredLoad, setLoad) {
[wrapper setHasRegisteredLoad];
}
RCT_REMAP_METHOD(unlockOrientation, unlock) {
[wrapper unlockOrientation];
}
@ -166,6 +174,14 @@ RCT_EXPORT_METHOD(saveFile:(NSString *)filePath
return [wrapper getWindowDimensions];
}
- (NSDictionary *)getHasRegisteredLoad {
return [wrapper getHasRegisteredLoad];
}
- (void)setHasRegisteredLoad {
[wrapper setHasRegisteredLoad];
}
- (void)lockPortrait {
[wrapper lockOrientation];
}

View file

@ -3,6 +3,7 @@ import React
@objc public class RNUtilsWrapper: NSObject {
@objc public weak var delegate: RNUtilsDelegate? = nil
@objc private var hasRegisteredLoad = false
deinit {
DispatchQueue.main.async {
@ -251,6 +252,14 @@ import React
group.wait()
return dimensions
}
@objc public func setHasRegisteredLoad() {
hasRegisteredLoad = true
}
@objc public func getHasRegisteredLoad() -> Dictionary<String, Any> {
return ["hasRegisteredLoad": hasRegisteredLoad]
}
@objc public func unlockOrientation() {
DispatchQueue.main.async {

View file

@ -41,6 +41,10 @@ export type WindowDimensionsChanged = Readonly<{
height: Double;
}>
type HasRegisteredLoadResponse = {
hasRegisteredLoad: boolean;
}
export interface Spec extends TurboModule {
readonly getConstants: () => Constants;
@ -55,6 +59,9 @@ export interface Spec extends TurboModule {
unlockOrientation: () => void;
lockPortrait: () => void;
getHasRegisteredLoad: () => HasRegisteredLoadResponse;
setHasRegisteredLoad: () => void;
deleteDatabaseDirectory: (databaseName: string, shouldRemoveDirectory: boolean) => DatabaseOperationResult;
renameDatabase: (databaseName: string, newDatabaseName: string) => DatabaseOperationResult;
deleteEntitiesFile: () => boolean;

View file

@ -145,6 +145,8 @@ jest.doMock('react-native', () => {
addListener: jest.fn(),
removeListeners: jest.fn(),
isRunningInSplitView: jest.fn().mockReturnValue({isSplit: false, isTablet: false}),
getHasRegisteredLoad: jest.fn().mockReturnValue({hasRegisteredLoad: false}),
setHasRegisteredLoad: jest.fn(),
getDeliveredNotifications: jest.fn().mockResolvedValue([]),
removeChannelNotifications: jest.fn().mockImplementation(),