Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add session sync callback #1292

Merged
merged 31 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1ebcf4a
feat(android): add session sync callback (#1281)
kholood-ea Sep 18, 2024
c1a8e5d
feat(ios): add session sync callback (#1282)
kholood-ea Sep 30, 2024
486b010
Revert "fix(ios): update network log signature"
kholood-ea Oct 2, 2024
201b490
chore(ios): update snapshot
kholood-ea Oct 2, 2024
038da9a
fix: ios network logging test after reverting
kholood-ea Oct 2, 2024
cf9db14
fix: convert sendEvent arg from writable to readable map
kholood-ea Oct 2, 2024
eaa6751
chore(android): update snapshot
kholood-ea Oct 2, 2024
efc5f6a
fix(android): refactor getSessionMetadataMap to tolerate null values
kholood-ea Oct 2, 2024
4901ebf
fix(ios): update fulfill exception wait time in test
kholood-ea Oct 2, 2024
e354e42
Merge remote-tracking branch 'origin/dev' into feat/session-sync-call…
kholood-ea Oct 2, 2024
cfcd55b
fix(android): convert session metadat map to readable map
kholood-ea Oct 4, 2024
e73f473
chore: update docs
kholood-ea Oct 4, 2024
daaedc0
fix: remove hot launch type
kholood-ea Oct 7, 2024
be32acd
fix: increase timeout expectation in test case
kholood-ea Oct 7, 2024
f4d097a
Revert "fix: increase timeout expectation in test case"
kholood-ea Oct 7, 2024
bb42692
feat(example): add features and buttons implementation (#1280)
YoussefFouadd Oct 16, 2024
2d34d8f
fix(android): add unknown launch type
kholood-ea Oct 28, 2024
3b85dcd
chore: update documentation
kholood-ea Oct 28, 2024
47dcb10
Merge branch 'refs/heads/dev' into feat/session-sync-callback
ahmedAlaaInstabug Nov 10, 2024
3b3a495
feat: upgrade to 14.0.0
ahmedAlaaInstabug Nov 10, 2024
9f2dea1
feat: upgrade to 14.0.0
ahmedAlaaInstabug Nov 10, 2024
7123d70
feat: upgrade to 14.0.0
ahmedAlaaInstabug Nov 10, 2024
18a253d
Merge branch 'refs/heads/dev' into feat/session-sync-callback
ahmedAlaaInstabug Nov 11, 2024
451f045
merge dev
ahmedAlaaInstabug Nov 11, 2024
aa8faa8
merge dev
ahmedAlaaInstabug Nov 11, 2024
72dac81
merge dev
ahmedAlaaInstabug Nov 11, 2024
6151ba1
fix: test case
ahmedAlaaInstabug Nov 12, 2024
1aa675f
chore: add changelog item
kholood-ea Nov 13, 2024
ed286eb
Update Podfile
ahmedAlaaInstabug Nov 13, 2024
f6f898d
Update Podfile
ahmedAlaaInstabug Nov 13, 2024
c0c5428
fix: test case
ahmedAlaaInstabug Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions android/src/main/java/com/instabug/reactlibrary/ArgsRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.instabug.library.invocation.InstabugInvocationEvent;
import com.instabug.library.invocation.util.InstabugFloatingButtonEdge;
import com.instabug.library.invocation.util.InstabugVideoRecordingButtonPosition;
import com.instabug.library.sessionreplay.model.SessionMetadata;
import com.instabug.library.ui.onboarding.WelcomeMessage;

import java.util.ArrayList;
Expand Down Expand Up @@ -58,6 +59,7 @@ static Map<String, Object> getAll() {
putAll(nonFatalExceptionLevel);
putAll(locales);
putAll(placeholders);
putAll(launchType);
}};
}

Expand Down Expand Up @@ -238,4 +240,18 @@ static Map<String, Object> getAll() {
put("team", Key.CHATS_TEAM_STRING_NAME);
put("insufficientContentMessage", Key.COMMENT_FIELD_INSUFFICIENT_CONTENT);
}};

public static ArgsMap<String> launchType = new ArgsMap<String>() {{
put("cold", SessionMetadata.LaunchType.COLD);
put("warm",SessionMetadata.LaunchType.WARM );
put("unknown","unknown");
}};

// Temporary workaround to be removed in future release
// This is used for mapping native `LaunchType` values into React Native enum values.
public static HashMap<String,String> launchTypeReversed = new HashMap<String,String>() {{
put(SessionMetadata.LaunchType.COLD,"cold");
put(SessionMetadata.LaunchType.WARM,"warm" );
}};

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ final class Constants {

final static String IBG_ON_NEW_MESSAGE_HANDLER = "IBGonNewMessageHandler";
final static String IBG_ON_NEW_REPLY_RECEIVED_CALLBACK = "IBGOnNewReplyReceivedCallback";
final static String IBG_SESSION_REPLAY_ON_SYNC_CALLBACK_INVOCATION = "IBGSessionReplayOnSyncCallback";

}
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
package com.instabug.reactlibrary;


import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.instabug.chat.Replies;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.instabug.library.OnSessionReplayLinkReady;
import com.instabug.library.SessionSyncListener;
import com.instabug.library.sessionreplay.SessionReplay;
import com.instabug.library.sessionreplay.model.SessionMetadata;
import com.instabug.reactlibrary.utils.EventEmitterModule;
import com.instabug.reactlibrary.utils.MainThreadHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import javax.annotation.Nonnull;

public class RNInstabugSessionReplayModule extends ReactContextBaseJavaModule {
public class RNInstabugSessionReplayModule extends EventEmitterModule {

public RNInstabugSessionReplayModule(ReactApplicationContext reactApplicationContext) {
super(reactApplicationContext);
}

@ReactMethod
public void addListener(String event) {
super.addListener(event);
}

@ReactMethod
public void removeListeners(Integer count) {
super.removeListeners(count);
}

@Nonnull
@Override
public String getName() {
Expand Down Expand Up @@ -79,7 +100,7 @@ public void run() {
e.printStackTrace();
}
}
});
});
}

@ReactMethod
Expand All @@ -97,6 +118,96 @@ public void onSessionReplayLinkReady(@Nullable String link) {
}
});

}

public ReadableMap getSessionMetadataMap(SessionMetadata sessionMetadata){
WritableMap params = Arguments.createMap();
params.putString("appVersion",sessionMetadata.getAppVersion());
params.putString("OS",sessionMetadata.getOs());
params.putString("device",sessionMetadata.getDevice());
params.putDouble("sessionDurationInSeconds",(double)sessionMetadata.getSessionDurationInSeconds());
params.putBoolean("hasLinkToAppReview",sessionMetadata.getLinkedToReview());
params.putArray("networkLogs",getNetworkLogsArray(sessionMetadata.getNetworkLogs()));

String launchType = sessionMetadata.getLaunchType();
Long launchDuration = sessionMetadata.getLaunchDuration();

if (launchType != null) {
params.putString("launchType",ArgsRegistry.launchTypeReversed.get(sessionMetadata.getLaunchType()) );
} else {
params.putString("launchType",ArgsRegistry.launchType.get("unknown"));
}

if (launchDuration != null) {
params.putDouble("launchDuration", (double)launchDuration);
} else {
params.putDouble("launchDuration", 0.0);
}

return params;
}

public ReadableArray getNetworkLogsArray(List<SessionMetadata.NetworkLog> networkLogList ) {
WritableArray networkLogs = Arguments.createArray();

if (networkLogList != null) {
for (SessionMetadata.NetworkLog log : networkLogList) {
WritableMap networkLog = Arguments.createMap();
networkLog.putString("url", log.getUrl());
networkLog.putDouble("duration", log.getDuration());
networkLog.putInt("statusCode", log.getStatusCode());

networkLogs.pushMap(networkLog);
}
}

return networkLogs;
}

private boolean shouldSync = true;
private CountDownLatch latch;
@ReactMethod
public void setSyncCallback() {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
try {
SessionReplay.setSyncCallback(new SessionSyncListener() {
@Override
public boolean onSessionReadyToSync(@NonNull SessionMetadata sessionMetadata) {

sendEvent(Constants.IBG_SESSION_REPLAY_ON_SYNC_CALLBACK_INVOCATION,getSessionMetadataMap(sessionMetadata));

latch = new CountDownLatch(1);

try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
return true;
}

return shouldSync;
}
});
}
catch(Exception e){
e.printStackTrace();
}

}
});
}

@ReactMethod
public void evaluateSync(boolean result) {
shouldSync = result;

if (latch != null) {
latch.countDown();
}
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

Expand All @@ -16,7 +17,7 @@ public EventEmitterModule(ReactApplicationContext context) {
}

@VisibleForTesting
public void sendEvent(String event, @Nullable WritableMap params) {
public void sendEvent(String event, @Nullable ReadableMap params) {
if (listenerCount > 0) {
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
package com.instabug.reactlibrary;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.Handler;
import android.os.Looper;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.instabug.chat.Replies;
import com.instabug.featuresrequest.ActionType;
import com.instabug.featuresrequest.FeatureRequests;
import com.instabug.library.Feature;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.instabug.library.OnSessionReplayLinkReady;
import com.instabug.library.SessionSyncListener;
import com.instabug.library.sessionreplay.SessionReplay;
import com.instabug.library.sessionreplay.model.SessionMetadata;
import com.instabug.reactlibrary.utils.MainThreadHandler;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;


public class RNInstabugSessionReplayModuleTest {
Expand All @@ -44,8 +48,8 @@ public class RNInstabugSessionReplayModuleTest {

// Mock Objects
private MockedStatic<Looper> mockLooper;
private MockedStatic <MainThreadHandler> mockMainThreadHandler;
private MockedStatic <SessionReplay> mockSessionReplay;
private MockedStatic<MainThreadHandler> mockMainThreadHandler;
private MockedStatic<SessionReplay> mockSessionReplay;

@Before
public void mockMainThreadHandler() throws Exception {
Expand Down Expand Up @@ -107,7 +111,7 @@ public void testSetInstabugLogsEnabled() {
@Test
public void testGetSessionReplayLink() {
Promise promise = mock(Promise.class);
String link="instabug link";
String link = "instabug link";

mockSessionReplay.when(() -> SessionReplay.getSessionReplayLink(any())).thenAnswer(
invocation -> {
Expand Down Expand Up @@ -136,5 +140,40 @@ public void testSetUserStepsEnabled() {
mockSessionReplay.verifyNoMoreInteractions();
}

@Test
public void testSetSyncCallback() throws Exception {
MockedStatic<Arguments> mockArguments = mockStatic(Arguments.class);
MockedConstruction<CountDownLatch> mockCountDownLatch = mockConstruction(CountDownLatch.class);
RNInstabugSessionReplayModule SRModule = spy(new RNInstabugSessionReplayModule(mock(ReactApplicationContext.class)));

final boolean shouldSync = true;
final AtomicBoolean actual = new AtomicBoolean();

mockArguments.when(Arguments::createMap).thenReturn(new JavaOnlyMap());

mockSessionReplay.when(() -> SessionReplay.setSyncCallback(any(SessionSyncListener.class)))
.thenAnswer((invocation) -> {
SessionSyncListener listener = (SessionSyncListener) invocation.getArguments()[0];
SessionMetadata metadata = mock(SessionMetadata.class);
actual.set(listener.onSessionReadyToSync(metadata));
return null;
});

doAnswer((invocation) -> {
SRModule.evaluateSync(shouldSync);
return null;
}).when(SRModule).sendEvent(eq(Constants.IBG_SESSION_REPLAY_ON_SYNC_CALLBACK_INVOCATION), any());

WritableMap params = Arguments.createMap();

SRModule.setSyncCallback();

assertEquals(shouldSync, actual.get());
verify(SRModule).sendEvent(Constants.IBG_SESSION_REPLAY_ON_SYNC_CALLBACK_INVOCATION, params);
mockSessionReplay.verify(() -> SessionReplay.setSyncCallback(any(SessionSyncListener.class)));

mockArguments.close();
mockCountDownLatch.close();
}

}
4 changes: 3 additions & 1 deletion examples/default/e2e/reportBug.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ it('reports a bug', async () => {
await waitFor(floatingButton).toBeVisible().withTimeout(30000);
await floatingButton.tap();

await getElement('reportBugMenuItem').tap();
const reportBugMenuItemButton = getElement('reportBugMenuItem');
await waitFor(reportBugMenuItemButton).toBeVisible().withTimeout(30000);
await reportBugMenuItemButton.tap();

await getElement('emailField').typeText(mockData.email);
await getElement('commentField').typeText(mockData.bugComment);
Expand Down
Loading