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

[Gutenberg] Add error boundary components and exception logging #20359

Merged
merged 25 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
43526c5
Add log exception handlers
jhnstn Mar 1, 2024
32db175
Prevent including React Native source in build
fluiddot Feb 29, 2024
fdaa982
Add gem `fastlane-plugin-sentry`
fluiddot Feb 29, 2024
9c4f788
Update build process to upload React Native source map to Sentry
fluiddot Feb 29, 2024
f132e70
Update Gutenberg Mobile reference
fluiddot Feb 29, 2024
61dec5a
Address issues identified by Dangermattic
fluiddot Mar 1, 2024
1a63c72
Update inline comment of `sentry_upload_sourcemap` invocation
fluiddot Mar 4, 2024
e87d6d8
remove past tense 'did'
jhnstn Mar 4, 2024
b31495c
Pass the exception event data through the editor lib
jhnstn Mar 6, 2024
ce6908c
Send the exception to the crash logger
jhnstn Mar 6, 2024
732fe40
Update GB and Tracks sources
jhnstn Mar 6, 2024
bdf0b0f
Merge branch 'trunk' into rnmobile/add/log-exception-to-crash-logging
jhnstn Mar 6, 2024
6291d17
Add missing import in `GutenbergEditorFragment`
fluiddot Mar 7, 2024
278fc9a
Update Gutenberg Mobile reference
fluiddot Mar 7, 2024
e63da63
Update Tracks reference
fluiddot Mar 7, 2024
6b0ad11
Update Tracks reference
fluiddot Mar 7, 2024
9408f29
Include Tracks Maven group in editor library
fluiddot Mar 7, 2024
b2ff729
Fix `sentry.properties` path in `upload_gutenberg_sourcemaps`
fluiddot Mar 7, 2024
08b5355
Add `dist` parameter to `sentry_upload_sourcemap`
fluiddot Mar 7, 2024
31beaac
Update tracks to latest version: 3.5.0
jhnstn Mar 8, 2024
82fe4b6
Update Gutenberg Mobile reference
fluiddot Mar 11, 2024
57cc01c
Copy bundle and source map files to a specific build folder
fluiddot Mar 12, 2024
fe45679
Exclude React Native source map when building the app
fluiddot Mar 12, 2024
17ba9b9
Remove unnecessary `build_asset_folder_name` variable in `upload_gute…
fluiddot Mar 12, 2024
eadc097
Remove unnecessary params from `upload_gutenberg_sourcemaps`
fluiddot Mar 12, 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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem 'nokogiri'

### Fastlane Plugins

gem 'fastlane-plugin-sentry'
gem 'fastlane-plugin-wpmreleasetoolkit', '~> 9.2'
# gem 'fastlane-plugin-wpmreleasetoolkit', path: '../../release-toolkit'
# gem 'fastlane-plugin-wpmreleasetoolkit', git: 'https://github.com/wordpress-mobile/release-toolkit', branch: ''
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-sentry (1.19.0)
os (~> 1.1, >= 1.1.4)
fastlane-plugin-wpmreleasetoolkit (9.4.0)
activesupport (>= 6.1.7.1)
buildkit (~> 1.5)
Expand Down Expand Up @@ -355,6 +357,7 @@ PLATFORMS
DEPENDENCIES
danger-dangermattic (~> 1.0)
fastlane (~> 2)
fastlane-plugin-sentry
fastlane-plugin-wpmreleasetoolkit (~> 9.2)
nokogiri
rmagick (~> 4.1)
Expand Down
20 changes: 20 additions & 0 deletions WordPress/build.gradle
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following #20359 (comment) , we had to make an adjustment in order to avoid including the source map file (index.android.bundle.map) in the app. This file is only needed in the build process to upload it to Sentry and shouldn't be in the resulting APK.

Originally, we tried using exclude '**/*.bundle.map' in this file (32db175) but for some reason, it was still adding the source map file. As a second approach, we are now copying the bundle and source map files to a separate folder and deleting the source map to avoid inclusion. The new folder will be used in the upload_gutenberg_sourcemaps function when uploading the files to Sentry.

@AliSoftware since you already took a look at the original approach (https://github.com/wordpress-mobile/WordPress-Android/pull/20359/files#r1510915719), I wonder if you could double-check that this approach is valid. Thanks for your help 🙇 !

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was focused on doing a CI deployment today, and only getting to this PR now, but I see it's already merged 🙂

I only skimmed the new code in build.gradle but your explanations above makes sense to me so I think the approach sounds good 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was focused on doing a CI deployment today, and only getting to this PR now, but I see it's already merged 🙂

No worries @AliSoftware. Yes, we finally decided to merge the PR (internal ref: p1710254914686709/1710180482.367029-slack-C06G8EGHE1H) to unblock other potential PRs, as the counterpart Gutenberg changes were already merged. In any case, if we identify improvements to be made to this approach, we'll be happy to apply them in a separate PR.

I only skimmed the new code in build.gradle but your explanations above makes sense to me so I think the approach sounds good 👍

Great, thanks for taking a look and validating the approach 🙇 !

Original file line number Diff line number Diff line change
Expand Up @@ -716,3 +716,23 @@ if (project.hasProperty("debugStoreFile")) {
}
}
}

// Copy React Native JavaScript bundle and source map so they can be upload it to the Crash logging
// service during the build process.
android {
applicationVariants.all { variant ->
variant.mergeAssetsProvider.configure {
doLast {
// Copy bundle and source map files
copy {
from(outputDir)
into("${buildDir}/react-native-bundle-source-map")
include("*.bundle", "*.bundle.map")
}

// Delete source maps
delete(fileTree(dir: outputDir, includes: ['**/*.bundle.map']))
}
}
}
}
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import androidx.viewpager.widget.ViewPager;

import com.automattic.android.tracks.crashlogging.CrashLogging;
import com.automattic.android.tracks.crashlogging.JsException;
import com.automattic.android.tracks.crashlogging.JsExceptionCallback;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
Expand Down Expand Up @@ -3856,4 +3858,8 @@ public LiveData<DialogVisibility> getSavingInProgressDialogVisibility() {
@Nullable private SavedInstanceDatabase getDB() {
return SavedInstanceDatabase.Companion.getDatabase(WordPress.getContext());
}

@Override public void onLogJsException(JsException exception, JsExceptionCallback onExceptionSend) {
mCrashLogging.sendJavaScriptReport(exception, onExceptionSend);
}
}
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ ext {
// libs
automatticAboutVersion = '1.4.0'
automatticRestVersion = '1.0.8'
automatticTracksVersion = '3.4.0'
gutenbergMobileVersion = 'v1.115.0-alpha3'
automatticTracksVersion = '3.5.0'
gutenbergMobileVersion = 'v1.115.0-alpha5'
wordPressAztecVersion = 'v2.0'
wordPressFluxCVersion = '2.70.0'
wordPressLoginVersion = '1.14.1'
Expand Down
35 changes: 35 additions & 0 deletions fastlane/lanes/build.rb
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wordpress-mobile/apps-infrastructure I'd appreciate it if someone could take a look at this approach for uploading the React Native's JavaScript source map file to Sentry. I'm mostly seeking validation of the approach, thoughts/feedback or better alternatives. Thank you very much for the help 🙇 !

To give some context on the approach: The source map is generated as part of the Gutenerg Mobile build process, the files are being passed as assets via the library (reference). Sentry uses the JavaScript bundle and the source map to symbolicate the stack trace and reference the exact code line that caused the exception (reference).

The approach followed here consists in:

  1. Add Gem fastlane-plugin-sentry to use the sentry_upload_sourcemap action.
  2. Add upload_gutenberg_sourcemaps helper function to build lanes.
  3. To invoke sentry_upload_sourcemap we need a set of parameters that are fetched with the following steps:
    1. Load Sentry properties to obtain the token, organization and project slugs.
    2. Locate the bundle and source map files passed as assets of the Gutenberg Mobile dependency, based on app, build flavor, and build type. Note that the path points to intermediates folder within the project's build path.
  4. Invoke the upload_gutenberg_sourcemaps in the different build lanes.

As a first approach, I tried to copy the bundle and source map files to a different folder during the asset merging step in the build process. However, I finally discarded this option because seemed unnecessary. Here you can see the changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach looks good to me; thanks for documenting it all with comments 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @AliSoftware for taking a look and validating the approach, I appreciate it 🙇 .

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
build_bundle(app: app, version_name: version_name, build_code: current_build_code, flavor: 'Vanilla', buildType: 'Release')

upload_build_to_play_store(app: app, version_name: version_name, track: 'production')
upload_gutenberg_sourcemaps(app: app, release_version: version_name)

create_gh_release(app: app, version_name: version_name) if options[:create_release]
end
Expand Down Expand Up @@ -105,6 +106,7 @@
build_bundle(app: app, version_name: version_name, build_code: current_build_code, flavor: 'Vanilla', buildType: 'Release')

upload_build_to_play_store(app: app, version_name: version_name, track: 'beta') if options[:upload_to_play_store]
upload_gutenberg_sourcemaps(app: app, release_version: version_name)

create_gh_release(app: app, version_name: version_name, prerelease: true) if options[:create_release]
end
Expand Down Expand Up @@ -217,6 +219,7 @@
)

upload_prototype_build(product: 'WordPress', version_name: version_name)
upload_gutenberg_sourcemaps(app: 'Wordpress', release_version: version_name)
end

#####################################################################################
Expand All @@ -240,6 +243,7 @@
)

upload_prototype_build(product: 'Jetpack', version_name: version_name)
upload_gutenberg_sourcemaps(app: 'Jetpack', release_version: version_name)
end

#####################################################################################
Expand Down Expand Up @@ -359,4 +363,35 @@ def generate_prototype_build_number
"#{branch}-#{commit}"
end
end

# Uploads the React Native JavaScript bundle and source map files.
# These files are provided by the Gutenberg Mobile library.
#
# @param [String] app App name, e.g. 'WordPress' or 'Jetpack'.
# @param [String] release_version Release version name to attach the files to in Sentry.
#
def upload_gutenberg_sourcemaps(app:, release_version:)
# Load Sentry properties
sentry_path = File.join(PROJECT_ROOT_FOLDER, 'WordPress', 'src', app.downcase, 'sentry.properties')
sentry_properties = JavaProperties.load(sentry_path)
sentry_token = sentry_properties[:'auth.token']
project_slug = sentry_properties[:'defaults.project']
org_slug = sentry_properties[:'defaults.org']

# Bundle and source map files are copied to a specific folder as part of the build process.
bundle_source_map_path = File.join(PROJECT_ROOT_FOLDER, 'WordPress', 'build', 'react-native-bundle-source-map')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This folder is created in:

copy {
from(outputDir)
into("${buildDir}/react-native-bundle-source-map")
include("*.bundle", "*.bundle.map")
}


sentry_upload_sourcemap(
auth_token: sentry_token,
org_slug: org_slug,
project_slug: project_slug,
version: release_version,
dist: current_build_code,
# When the React native bundle is generated, the source map file references include the local machine path;
# With the `rewrite` and `strip_common_prefix` options, Sentry automatically strips this part.
rewrite: true,
strip_common_prefix: true,
sourcemap: bundle_source_map_path
)
end
end
3 changes: 3 additions & 0 deletions libs/editor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ repositories {
includeGroup "org.wordpress.aztec"
includeGroup "org.wordpress.gutenberg-mobile"
includeGroupByRegex "org.wordpress.react-native-libraries.*"
includeGroup "com.automattic"
includeGroup "com.automattic.tracks"
}
}
maven {
Expand Down Expand Up @@ -104,6 +106,7 @@ dependencies {
implementation "com.google.android.material:material:$googleMaterialVersion"
implementation "com.android.volley:volley:$androidVolleyVersion"
implementation "com.google.code.gson:gson:$googleGsonVersion"
implementation "com.automattic:Automattic-Tracks-Android:$automatticTracksVersion"

lintChecks "org.wordpress:lint:$wordPressLintVersion"
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import androidx.lifecycle.LiveData;

import com.android.volley.toolbox.ImageLoader;
import com.automattic.android.tracks.crashlogging.JsException;
import com.automattic.android.tracks.crashlogging.JsExceptionCallback;

import org.wordpress.android.editor.gutenberg.DialogVisibilityProvider;
import org.wordpress.android.util.helpers.MediaFile;
Expand Down Expand Up @@ -232,6 +234,8 @@ public interface EditorFragmentListener extends DialogVisibilityProvider {
void onToggleRedo(boolean isDisabled);

void onBackHandlerButton();

void onLogJsException(JsException jsException, JsExceptionCallback onSendJsException);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidRequestUnsupportedBlockFallbackListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidSendButtonPressedActionListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnImageFullscreenPreviewListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnLogExceptionListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaUploadQueryListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnFocalPointPickerTooltipShownEventListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaEditorListener;
Expand Down Expand Up @@ -95,6 +96,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener
OnToggleRedoButtonListener onToggleRedoButtonListener,
OnConnectionStatusEventListener onConnectionStatusEventListener,
OnBackHandlerEventListener onBackHandlerEventListener,
OnLogExceptionListener onLogExceptionListener,
boolean isDarkMode) {
mWPAndroidGlueCode.attachToContainer(
viewGroup,
Expand All @@ -120,6 +122,7 @@ public void attachToContainer(ViewGroup viewGroup, OnMediaLibraryButtonListener
onToggleRedoButtonListener,
onConnectionStatusEventListener,
onBackHandlerEventListener,
onLogExceptionListener,
isDarkMode);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import androidx.lifecycle.LiveData;

import com.android.volley.toolbox.ImageLoader;
import com.automattic.android.tracks.crashlogging.JsException;
import com.automattic.android.tracks.crashlogging.JsExceptionCallback;
import com.automattic.android.tracks.crashlogging.JsExceptionStackTraceElement;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
Expand All @@ -47,10 +50,10 @@
import org.wordpress.android.editor.EditorThemeUpdateListener;
import org.wordpress.android.editor.LiveTextWatcher;
import org.wordpress.android.editor.R;
import org.wordpress.android.editor.savedinstance.SavedInstanceDatabase;
import org.wordpress.android.editor.WPGutenbergWebViewActivity;
import org.wordpress.android.editor.gutenberg.GutenbergDialogFragment.GutenbergDialogNegativeClickInterface;
import org.wordpress.android.editor.gutenberg.GutenbergDialogFragment.GutenbergDialogPositiveClickInterface;
import org.wordpress.android.editor.savedinstance.SavedInstanceDatabase;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.DateTimeUtils;
Expand All @@ -62,14 +65,16 @@
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
import org.wordpress.aztec.IHistoryListener;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.LogExceptionCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergEmbedWebViewActivity;
import org.wordpress.mobile.WPAndroidGlue.GutenbergJsException;
import org.wordpress.mobile.WPAndroidGlue.Media;
import org.wordpress.mobile.WPAndroidGlue.MediaOption;
import org.wordpress.mobile.WPAndroidGlue.RequestExecutor;
import org.wordpress.mobile.WPAndroidGlue.ShowSuggestionsUtil;
import org.wordpress.mobile.WPAndroidGlue.UnsupportedBlock;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnBlockTypeImpressionsEventListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnBackHandlerEventListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnBlockTypeImpressionsEventListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnConnectionStatusEventListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnContentInfoReceivedListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnCustomerSupportOptionsListener;
Expand All @@ -80,6 +85,7 @@
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidRequestPreviewListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidRequestUnsupportedBlockFallbackListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnGutenbergDidSendButtonPressedActionListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnLogExceptionListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnMediaLibraryButtonListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnReattachMediaUploadQueryListener;
import org.wordpress.mobile.WPAndroidGlue.WPAndroidGlueCode.OnSetFeaturedImageListener;
Expand All @@ -88,9 +94,11 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static org.wordpress.mobile.WPAndroidGlue.Media.createRNMediaUsingMimeType;

Expand Down Expand Up @@ -517,6 +525,40 @@ public void onGotoCustomerSupportOptions() {
}
},

new OnLogExceptionListener() {
@Override public void onLogException(GutenbergJsException exception,
LogExceptionCallback logExceptionCallback) {
List<JsExceptionStackTraceElement> stackTraceElements = exception.getStackTrace().stream().map(
stackTrace -> {
return new JsExceptionStackTraceElement(
stackTrace.getFileName(),
stackTrace.getLineNumber(),
stackTrace.getColNumber(),
stackTrace.getFunction()
);
}).collect(Collectors.toList());

JsException jsException = new JsException(
exception.getType(),
exception.getMessage(),
stackTraceElements,
exception.getContext(),
exception.getTags(),
exception.isHandled(),
exception.getHandledBy()
);

JsExceptionCallback callback = new JsExceptionCallback() {
@Override
public void onReportSent(boolean success) {
logExceptionCallback.onLogException(success);
}
};

mEditorFragmentListener.onLogJsException(jsException, callback);
}
},

GutenbergUtils.isDarkMode(getActivity()));

// request dependency injection. Do this after setting min/max dimensions
Expand Down
Loading