Skip to content

feat: add proactive bug reporting #1359

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

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
Open
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased](https://github.com/Instabug/Instabug-React-Native/compare/v14.1.0...dev)

### Added

- Add support for proactive bug-reporting ([#1359](https://github.com/Instabug/Instabug-React-Native/pull/1359))

## [14.1.0](https://github.com/Instabug/Instabug-React-Native/compare/v14.0.0...v14.1.0) (January 2, 2025)

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.instabug.bug.BugReporting;
import com.instabug.bug.ProactiveReportingConfigs;
import com.instabug.bug.invocation.Option;
import com.instabug.library.Feature;
import com.instabug.library.OnSdkDismissCallback;
Expand Down Expand Up @@ -49,6 +50,7 @@ public void removeListeners(Integer count) {

/**
* Enable or disable all BugReporting related features.
*
* @param isEnabled boolean indicating enabled or disabled.
*/
@ReactMethod
Expand Down Expand Up @@ -116,6 +118,7 @@ public void run() {

/**
* Enables or disables view hierarchy in the dashboard.
*
* @param isEnabled boolean indicating enabled or disabled.
*/
@ReactMethod
Expand Down Expand Up @@ -160,10 +163,10 @@ public void run() {
/**
* Sets whether attachments in bug reporting and in-app messaging are enabled or not.
*
* @param screenshot A boolean to enable or disable screenshot attachments.
* @param {boolean} extraScreenShot A boolean to enable or disable extra screenshot attachments.
* @param {boolean} galleryImage A boolean to enable or disable gallery image attachments.
* @param {boolean} screenRecording A boolean to enable or disable screen recording attachments.
* @param screenshot A boolean to enable or disable screenshot attachments.
* @param {boolean} extraScreenShot A boolean to enable or disable extra screenshot attachments.
* @param {boolean} galleryImage A boolean to enable or disable gallery image attachments.
* @param {boolean} screenRecording A boolean to enable or disable screen recording attachments.
*/
@ReactMethod
public void setEnabledAttachmentTypes(final boolean screenshot, final boolean extraScreenshot, final boolean
Expand Down Expand Up @@ -235,7 +238,7 @@ public void run() {
* UI changes before the SDK's UI is shown.
*
* @param onInvokeHandler - A callback that gets executed before
* invoking the SDK
* invoking the SDK
*/
@ReactMethod
public void setOnInvokeHandler(final Callback onInvokeHandler) {
Expand All @@ -258,7 +261,8 @@ public void onInvoke() {

/**
* Sets the position of the Instabug floating button on the screen.
* @param floatingButtonEdge left or right edge of the screen.
*
* @param floatingButtonEdge left or right edge of the screen.
* @param floatingButtonOffset integer offset from the left or right edge of the screen.
*/
@ReactMethod
Expand All @@ -280,7 +284,7 @@ public void run() {
* UI changes after the SDK's UI is dismissed.
*
* @param handler - A callback to get executed after
* dismissing the SDK.
* dismissing the SDK.
*/
@ReactMethod
public void setOnSDKDismissedHandler(final Callback handler) {
Expand Down Expand Up @@ -328,6 +332,7 @@ public void run() {

/**
* Sets the enabled report types to be shown in the prompt. Bug or Feedback or both.
*
* @param types
* @see BugReporting.ReportType
*/
Expand Down Expand Up @@ -356,8 +361,9 @@ public void run() {

/**
* Shows a bug or feedback report with optional options.
*
* @param reportType Bug or Feedback.
* @param options array of options
* @param options array of options
* @see BugReporting.ReportType
* @see Option
*/
Expand All @@ -375,11 +381,12 @@ public void run() {
}

/**
* Adds a disclaimer text within the bug reporting form, which can include hyperlinked text.
* @param text String text.
*/
* Adds a disclaimer text within the bug reporting form, which can include hyperlinked text.
*
* @param text String text.
*/
@ReactMethod
public void setDisclaimerText(final String text){
public void setDisclaimerText(final String text) {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
Expand All @@ -389,12 +396,13 @@ public void run() {
}

/**
* Sets a minimum number of characters as a requirement for the comments field in the different report types.
* @param limit int number of characters.
* @param reportTypes (Optional) Array of reportType. If it's not passed, the limit will apply to all report types.
*/
* Sets a minimum number of characters as a requirement for the comments field in the different report types.
*
* @param limit int number of characters.
* @param reportTypes (Optional) Array of reportType. If it's not passed, the limit will apply to all report types.
*/
@ReactMethod
public void setCommentMinimumCharacterCount(final int limit, final ReadableArray reportTypes){
public void setCommentMinimumCharacterCount(final int limit, final ReadableArray reportTypes) {
MainThreadHandler.runOnMainThread(new Runnable() {
@SuppressLint("WrongConstant")
@Override
Expand All @@ -415,4 +423,28 @@ public void run() {
}
});
}

/**
* prompts end users to submit their feedback after our SDK automatically detects a frustrating experience.
*
* @param enabled controls the state of the feature
* @param modalDelayAfterDetection controls the time gap between detecting a frustrating experience
* @param gapBetweenModals controls the time gap between showing 2 proactive reporting dialogs in seconds
*/
@ReactMethod
public void setProactiveReportingConfigurations(final boolean enabled, final int gapBetweenModals, final int modalDelayAfterDetection) {
MainThreadHandler.runOnMainThread(new Runnable() {
@Override
public void run() {
ProactiveReportingConfigs configs = new ProactiveReportingConfigs.Builder()
.setGapBetweenModals(gapBetweenModals) // Time in seconds
.setModalDelayAfterDetection(modalDelayAfterDetection) // Time in seconds
.isEnabled(enabled) //Enable/disable
.build();
BugReporting.setProactiveReportingConfigurations(configs);


}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.instabug.bug.BugReporting;
import com.instabug.bug.ProactiveReportingConfigs;
import com.instabug.library.Feature;
import com.instabug.library.OnSdkDismissCallback;
import com.instabug.library.extendedbugreport.ExtendedBugReport;
Expand All @@ -21,6 +22,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.mockito.internal.verification.VerificationModeFactory;
import org.mockito.invocation.InvocationOnMock;
Expand All @@ -30,7 +32,10 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
Expand All @@ -45,9 +50,9 @@ public class RNInstabugBugReportingModuleTest {

// Mock Objects
private MockedStatic<Looper> mockLooper;
private MockedStatic <MainThreadHandler> mockMainThreadHandler;
private MockedStatic <BugReporting> mockBugReporting;
private MockedStatic <InstabugUtil> mockInstabugUtil;
private MockedStatic<MainThreadHandler> mockMainThreadHandler;
private MockedStatic<BugReporting> mockBugReporting;
private MockedStatic<InstabugUtil> mockInstabugUtil;

@Before
public void mockMainThreadHandler() throws Exception {
Expand All @@ -72,6 +77,7 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable {
doAnswer(handlerPostAnswer).when(MainThreadHandler.class);
MainThreadHandler.runOnMainThread(any(Runnable.class));
}

@After
public void tearDown() {
// Remove static mocks
Expand All @@ -91,7 +97,7 @@ public void tearDown() {
// when
bugReportingModule.setShakingThresholdForAndroid(shakingThreshold);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));
BugReporting.setShakingThreshold(shakingThreshold);
}

Expand All @@ -102,7 +108,7 @@ public void tearDown() {
// when
bugReportingModule.setEnabled(false);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setState(Feature.State.DISABLED);
}
Expand All @@ -114,7 +120,7 @@ public void tearDown() {
// when
bugReportingModule.setEnabled(true);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setState(Feature.State.ENABLED);
}
Expand All @@ -126,7 +132,7 @@ public void tearDown() {
// when
bugReportingModule.setAutoScreenRecordingEnabled(true);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setAutoScreenRecordingEnabled(true);
}
Expand All @@ -138,7 +144,7 @@ public void tearDown() {
// when
bugReportingModule.setViewHierarchyEnabled(true);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setViewHierarchyState(Feature.State.ENABLED);
}
Expand All @@ -150,7 +156,7 @@ public void tearDown() {
// when
bugReportingModule.setViewHierarchyEnabled(false);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setViewHierarchyState(Feature.State.DISABLED);
}
Expand All @@ -162,7 +168,7 @@ public void tearDown() {
// when
bugReportingModule.setEnabledAttachmentTypes(true, true, false, true);
// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setAttachmentTypesEnabled(true, true, false, true);
}
Expand All @@ -179,7 +185,7 @@ public void tearDown() {

// then
for (ExtendedBugReport.State extendedBugReportMode : args.values()) {
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));
BugReporting.setExtendedBugReportState(extendedBugReportMode);
}
}
Expand All @@ -198,7 +204,7 @@ public void tearDown() {
bugReportingModule.setInvocationEvents(actualArray);

// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));
BugReporting.setInvocationEvents(args.values().toArray(new InstabugInvocationEvent[0]));
}

Expand All @@ -215,11 +221,11 @@ public void tearDown() {
bugReportingModule.setOptions(actualArray);

// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));
int option1 = args.get(keysArray[0]);
int option2 = args.get(keysArray[1]);
BugReporting.setOptions(option1);
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));
BugReporting.setOptions(option2);
}

Expand Down Expand Up @@ -255,7 +261,8 @@ public Object answer(InvocationOnMock invocation) {
((OnSdkDismissCallback) invocation.getArguments()[0])
.call(OnSdkDismissCallback.DismissType.CANCEL, OnSdkDismissCallback.ReportType.BUG);
return null;
}});
}
});
bugReportingModule.setOnSDKDismissedHandler(null);

// then
Expand All @@ -280,7 +287,7 @@ public Object answer(InvocationOnMock invocation) {
bugReportingModule.setReportTypes(actualArray);

// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

int type1 = args.get(keysArray[0]);
int type2 = args.get(keysArray[1]);
Expand All @@ -295,9 +302,9 @@ public Object answer(InvocationOnMock invocation) {

// when
bugReportingModule.setVideoRecordingFloatingButtonPosition(keysArray[0]);

// then
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));
InstabugVideoRecordingButtonPosition position = (InstabugVideoRecordingButtonPosition) args.get(keysArray[0]);
BugReporting.setVideoRecordingFloatingButtonPosition(position);
}
Expand All @@ -319,13 +326,13 @@ public Object answer(InvocationOnMock invocation) {
// then
int option1 = optionsArgs.get(keysArray[0]);
int option2 = optionsArgs.get(keysArray[1]);
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setOptions(option1);
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.setOptions(option2);
verify(BugReporting.class,VerificationModeFactory.times(1));
verify(BugReporting.class, VerificationModeFactory.times(1));

BugReporting.show(reportTypeArgs.get(reportTypeKeys[0]));
}
Expand Down Expand Up @@ -359,7 +366,27 @@ public Object answer(InvocationOnMock invocation) {
// then
verify(BugReporting.class, VerificationModeFactory.times(1));
int type1 = args.get(keysArray[0]);

BugReporting.setCommentMinimumCharacterCount(count, type1);
}

@Test
public void testSetProactiveReportingConfigurations() {
// given
boolean enabled = true;
int gapBetweekDialogs = 20;
int modeDelay = 30;

// when
bugReportingModule.setProactiveReportingConfigurations(enabled, gapBetweekDialogs, modeDelay);

// then
mockBugReporting.verify(() -> BugReporting.setProactiveReportingConfigurations(argThat(config ->
config.getModalsGap() == gapBetweekDialogs &&
config.getDetectionGap() == modeDelay &&
config.isEnabled() == enabled
)));


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
buildConfiguration = "Release"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
Loading