diff --git a/README.md b/README.md
index 68055707b..b6bb24b34 100644
--- a/README.md
+++ b/README.md
@@ -2224,3 +2224,34 @@ Alternatively, a different folder can be specified by setting the `MLS_ASSETS_FO
For a form named `example_form` the generated assets will follow the following naming convention : `example_form.json` and `example_form.properties`.
The properties file can then be copied over to the `resources` folder of your Android project under `src/main`. The placeholder-injected JsonForm will typically be copied over to the `assets` folder of your Android project (although not mandatory).
+
+## How to use Sentry for OpenSRP Client Performance Monitoring
+
+This section explains how to setup performance monitoring with Sentry on opensrp clients if you have added opensrp-native-forms library as a dependency. Instructions are given in this article on how to initialize Sentry in the opensrp client apps and how to check for the performance data or transactions on the Sentry dashboard.
+
+> ### Note
+>
+> * You need to have already setup a [project](https://docs.sentry.io/product/sentry-basics/integrate-frontend/create-new-project/) and dashboard with the necessary access permissions, and
+> * are able to obtain the dsn of this project through `[Project] > Settings > Client Keys (DSN)`. This will be required later for configuration and logging.
+
+### Instructions
+
+1. Add and update opensrp-native-forms library to version above v3.0.4-SNAPSHOT
+
+2. Sync and rebuild project to ensure new dependencies are loaded. Sentry would come as dependency to opensrp-native-forms and would load up automatically. Run your application, and confirm no logs are sent to the sentry dashboard
+
+3. There are a couple of configurations that would need to be setup first for logs to. show up to the dashboard. These are:
+ * DSN (Data Source Name); you can find your DSN in your Sentry project settings by navigating to `[Project] > Settings > Client Keys (DSN)` in sentry.io.
+
+ * Environment; unique name that would be used to tag logs
+
+ In your app’s Application class, add the following code snippet in onCreate with the placeholder values replaced . Run your application once more
+
+ ```Java
+ SentryAndroid.init(this, options -> {
+ options.setEnvironment("YOUR_ENVIRONMENT_NAME");
+ options.setDsn("YOUR_DSN_NAME_LINK");
+ })
+ ```
+
+4. Logs in performance monitoring should show up in the Sentry dashboard. Use the Environment tag set from the previous step to filter environment.
diff --git a/android-json-form-wizard/build.gradle b/android-json-form-wizard/build.gradle
index 60f0faed7..c1867697d 100644
--- a/android-json-form-wizard/build.gradle
+++ b/android-json-form-wizard/build.gradle
@@ -33,6 +33,13 @@ android {
multiDexEnabled true
testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
+ javaCompileOptions {
+ annotationProcessorOptions {
+ includeCompileClasspath = true
+ }
+ }
+
+ multiDexKeepProguard file('multidex-config.pro')
}
buildTypes {
@@ -58,6 +65,14 @@ android {
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
}
+ configurations.all {
+ resolutionStrategy.force 'com.android.support:design:28.0.0'
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
}
@@ -137,6 +152,8 @@ dependencies {
implementation "org.greenrobot:eventbus:3.2.0"
implementation 'androidx.multidex:multidex:2.0.1'
+ api 'io.sentry:sentry-android:5.0.1'
+
// PowerMock
def powerMockVersion = '2.0.9'
testImplementation "org.powermock:powermock-module-junit4:$powerMockVersion"
@@ -210,3 +227,6 @@ task javadoc(type: Javadoc) {
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.compile
}
+
+
+
diff --git a/android-json-form-wizard/src/main/AndroidManifest.xml b/android-json-form-wizard/src/main/AndroidManifest.xml
index 9bb98439f..abfe7dcb7 100644
--- a/android-json-form-wizard/src/main/AndroidManifest.xml
+++ b/android-json-form-wizard/src/main/AndroidManifest.xml
@@ -24,6 +24,7 @@
android:theme="@style/NativeFormsAppTheme"
tools:replace="android:theme">
+
+
diff --git a/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/customviews/DatePickerDialog.java b/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/customviews/DatePickerDialog.java
index b7b14f1d5..e00d2afdd 100644
--- a/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/customviews/DatePickerDialog.java
+++ b/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/customviews/DatePickerDialog.java
@@ -6,6 +6,8 @@
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -27,6 +29,10 @@
public class DatePickerDialog extends DialogFragment {
private DatePicker datePicker;
+
+ private Button okButton;
+
+ private Button cancelButton;
private android.app.DatePickerDialog.OnDateSetListener onDateSetListener;
private DialogInterface.OnShowListener onShowListener;
private Date date;
@@ -67,9 +73,6 @@ public void setOnShowListener(DialogInterface.OnShowListener onShowListener_) {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup dialogView = (ViewGroup) inflater.inflate(R.layout.native_form_dialog_date_picker, container, false);
- Button cancelButton;
- Button okButton;
-
setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
@@ -179,4 +182,14 @@ public DatePicker getDatePicker() {
public void setNumericDatePicker(boolean numericDatePicker) {
isNumericDatePicker = numericDatePicker;
}
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public Button getOkButton(){
+ return okButton;
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public Button getCancelButton(){
+ return cancelButton;
+ }
}
diff --git a/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/utils/FormUtils.java b/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/utils/FormUtils.java
index 6744f8d74..00cefe542 100644
--- a/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/utils/FormUtils.java
+++ b/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/utils/FormUtils.java
@@ -1139,28 +1139,30 @@ public JSONArray getSecondaryValues(JSONObject jsonObject, String type) {
*/
public JSONArray getFormFields(String stepName, Context context) {
Activity activity = (Activity) context;
- JsonApi jsonApi = (JsonApi) activity;
JSONArray fields = new JSONArray();
- JSONObject mJSONObject = jsonApi.getmJSONObject();
- if (mJSONObject != null) {
- JSONObject parentJson = jsonApi.getStep(stepName);
- try {
- if (parentJson.has(JsonFormConstants.SECTIONS) && parentJson
- .get(JsonFormConstants.SECTIONS) instanceof JSONArray) {
- JSONArray sections = parentJson.getJSONArray(JsonFormConstants.SECTIONS);
- for (int i = 0; i < sections.length(); i++) {
- JSONObject sectionJson = sections.getJSONObject(i);
- if (sectionJson.has(JsonFormConstants.FIELDS)) {
- fields = concatArray(fields, sectionJson.getJSONArray(JsonFormConstants.FIELDS));
+ if (activity instanceof JsonApi) {
+ JsonApi jsonApi = (JsonApi) activity;
+ JSONObject mJSONObject = jsonApi.getmJSONObject();
+ if (mJSONObject != null) {
+ JSONObject parentJson = jsonApi.getStep(stepName);
+ try {
+ if (parentJson.has(JsonFormConstants.SECTIONS) && parentJson
+ .get(JsonFormConstants.SECTIONS) instanceof JSONArray) {
+ JSONArray sections = parentJson.getJSONArray(JsonFormConstants.SECTIONS);
+ for (int i = 0; i < sections.length(); i++) {
+ JSONObject sectionJson = sections.getJSONObject(i);
+ if (sectionJson.has(JsonFormConstants.FIELDS)) {
+ fields = concatArray(fields, sectionJson.getJSONArray(JsonFormConstants.FIELDS));
+ }
}
- }
- } else if (parentJson.has(JsonFormConstants.FIELDS) && parentJson
- .get(JsonFormConstants.FIELDS) instanceof JSONArray) {
- fields = parentJson.getJSONArray(JsonFormConstants.FIELDS);
+ } else if (parentJson.has(JsonFormConstants.FIELDS) && parentJson
+ .get(JsonFormConstants.FIELDS) instanceof JSONArray) {
+ fields = parentJson.getJSONArray(JsonFormConstants.FIELDS);
+ }
+ } catch (JSONException e) {
+ Timber.e(e);
}
- } catch (JSONException e) {
- Timber.e(e);
}
}
return fields;
diff --git a/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/widgets/NativeRadioButtonFactory.java b/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/widgets/NativeRadioButtonFactory.java
index ad4d459b0..638fc2535 100644
--- a/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/widgets/NativeRadioButtonFactory.java
+++ b/android-json-form-wizard/src/main/java/com/vijay/jsonwizard/widgets/NativeRadioButtonFactory.java
@@ -64,7 +64,7 @@
*/
public class NativeRadioButtonFactory implements FormWidgetFactory {
- private static final String TAG = NativeRadioButtonFactory.class.getCanonicalName();
+ public static final String TAG = NativeRadioButtonFactory.class.getCanonicalName();
private final FormUtils formUtils = new FormUtils();
private final CustomTextViewClickListener customTextViewClickListener = new CustomTextViewClickListener();
private RadioButton radioButton;
diff --git a/android-json-form-wizard/src/multidex-config.pro b/android-json-form-wizard/src/multidex-config.pro
new file mode 100644
index 000000000..94584adff
--- /dev/null
+++ b/android-json-form-wizard/src/multidex-config.pro
@@ -0,0 +1,2 @@
+-keep class io.sentry.android.core.SentryAndroidOptions
+-keep class io.sentry.android.ndk.SentryNdk
\ No newline at end of file
diff --git a/android-json-form-wizard/src/test/java/com/vijay/jsonwizard/presenters/JsonWizardFormFragmentPresenterRoboelectricTest.java b/android-json-form-wizard/src/test/java/com/vijay/jsonwizard/presenters/JsonWizardFormFragmentPresenterRoboelectricTest.java
index 0c5579ab2..6913ebdbd 100644
--- a/android-json-form-wizard/src/test/java/com/vijay/jsonwizard/presenters/JsonWizardFormFragmentPresenterRoboelectricTest.java
+++ b/android-json-form-wizard/src/test/java/com/vijay/jsonwizard/presenters/JsonWizardFormFragmentPresenterRoboelectricTest.java
@@ -1,12 +1,12 @@
package com.vijay.jsonwizard.presenters;
import android.app.Activity;
+import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.MediaStore;
-import androidx.appcompat.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
@@ -48,6 +48,7 @@
import static com.vijay.jsonwizard.presenters.JsonFormFragmentPresenter.RESULT_LOAD_IMG;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -159,10 +160,10 @@ public void testOnClickShouldSOpenPictureTakingActivity() {
}
@Test
- public void testOnClickShouldDisplayDatePickerDialog() {
+ public void testOnClickShouldDisplayDatePickerDialog() throws InterruptedException {
LinearLayout view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.native_form_compound_button_parent, null);
CustomTextView customTextView = new CustomTextView(context);
- Activity activity = Robolectric.buildActivity(AppCompatActivity.class).create().get();
+ Activity activity = Robolectric.buildActivity(Activity.class).create().get();
RadioButton radioButton = new RadioButton(context);
view.setTag(R.id.specify_textview, customTextView);
view.setTag(R.id.native_radio_button, radioButton);
@@ -175,9 +176,17 @@ public void testOnClickShouldDisplayDatePickerDialog() {
view.setTag(R.id.specify_widget, JsonFormConstants.DATE_PICKER);
view.setTag(R.id.option_json_object, new JSONObject());
formFragmentPresenter.onClick(view);
- DatePickerDialog dialogFragment = (DatePickerDialog) activity.getFragmentManager()
- .findFragmentByTag(NativeRadioButtonFactory.class.getCanonicalName());
- assertNotNull(view);
+ Thread.sleep(3000);
+ DatePickerDialog dialogFragment = null;
+ FragmentManager fragmentManager = activity.getFragmentManager();
+ if (fragmentManager != null){
+ dialogFragment = (DatePickerDialog) fragmentManager.findFragmentByTag(NativeRadioButtonFactory.TAG);
+ }
+ assertNotNull(dialogFragment);
+
+ View okButton = dialogFragment.getOkButton();
+ okButton.performClick();
+ assertTrue(radioButton.getText().length() > 0);
}
}
diff --git a/gradle.properties b/gradle.properties
index 5abb92c86..906886943 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-VERSION_NAME=3.1.1-SNAPSHOT
+VERSION_NAME=3.2.0-SNAPSHOT
VERSION_CODE=1
GROUP=org.smartregister
POM_SETTING_DESCRIPTION=OpenSRP Client Native Form Json Wizard
diff --git a/sample/build.gradle b/sample/build.gradle
index 81bfb98d6..5f5e2113b 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -29,6 +29,8 @@ android {
versionCode 1
versionName "1.0"
multiDexEnabled true
+
+ buildConfigField("String", "SENTRY_DSN", "\"" + getProps("sentry.dsn") + "\"")
}
buildTypes {
@@ -60,3 +62,14 @@ dependencies {
implementation 'org.smartregister:opensrp-client-utils:0.0.6-SNAPSHOT'
}
+
+// get property from local.properties
+def getProps(String propName) {
+ def propsFile = rootProject.file('local.properties')
+ if (propsFile.exists()) {
+ def props = new Properties()
+ props.load(new FileInputStream(propsFile))
+ return props[propName]
+ }
+ return "";
+}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 75c11a6ab..1eae6330d 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
+ android:name=".MainApplication"
android:theme="@style/NativeFormsAppTheme">
{
+ options.setEnvironment("opensrp-native-form-sample");
+ options.setDsn(BuildConfig.SENTRY_DSN.trim());
+ });
+ }
+
+ }
+}