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

Provide automatic breadcrumbs and transactions for click/scroll events for Compose #2390

Merged
merged 31 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8be4802
Add integration for Compose clickables
markushi Nov 23, 2022
7efaaff
Fix only auto-create breadcrumbs for clicks when option is enabled
markushi Nov 23, 2022
56325fd
Fix missing .api file update and formatting
markushi Nov 23, 2022
b58a8d7
Only create clickable transaction when there's none running
markushi Nov 24, 2022
611beca
Determine Compose click and scroll targets at runtime
markushi Nov 30, 2022
abcd310
Merge branch 'main' into feat/compose-ui-transactions
markushi Nov 30, 2022
f185262
Remove obsolete implementation
markushi Nov 30, 2022
89260ac
Update sentry/src/main/java/io/sentry/SentryOptions.java
markushi Nov 30, 2022
3dfe721
Format code
getsentry-bot Nov 30, 2022
409d152
Enable UserInteractionIntegration according to settings
markushi Dec 1, 2022
50a302d
Merge branch 'feat/compose-ui-transactions' of github.com:getsentry/s…
markushi Dec 1, 2022
7267040
Re-structure code, use reflection for compose click/scroll transactions
markushi Dec 5, 2022
27ba08a
Add UI tests for Jetpack Compose user interaction breadcrumbs
markushi Dec 7, 2022
f569e29
Merge branch 'main' of github.com:getsentry/sentry-java into feat/com…
markushi Dec 7, 2022
2ffbc8b
Remove obsolete dependencies
markushi Dec 7, 2022
3d8b833
Add changelog entry
markushi Dec 7, 2022
a979bf3
Update sentry-android-core/src/main/java/io/sentry/android/core/inter…
markushi Dec 9, 2022
ed79435
Refactor based on PR comments
markushi Dec 9, 2022
c106ca2
Merge branch 'feat/compose-ui-transactions' of github.com:getsentry/s…
markushi Dec 9, 2022
44c7096
Adapts code to PR comments
markushi Dec 9, 2022
8438cb4
Port ComposeGestureTargetLocator back to Java
markushi Dec 12, 2022
6cead69
Integrate PR comments
markushi Dec 13, 2022
9f4600e
Add README to sentry-compose-helper module
markushi Dec 13, 2022
306bd0a
Merge branch 'main' into feat/compose-ui-transactions
markushi Dec 13, 2022
50f6792
Fix failing tests
markushi Dec 13, 2022
1c582d8
Format code
getsentry-bot Dec 13, 2022
c1753c0
Fix missing code formatting
markushi Dec 13, 2022
f846c6c
Merge branch 'feat/compose-ui-transactions' of github.com:getsentry/s…
markushi Dec 13, 2022
14fddbd
Add potential fix for saucelabs ui tests
markushi Dec 14, 2022
534cda5
Fix unused imports
markushi Dec 14, 2022
47bc669
Merge branch 'main' into feat/compose-ui-transactions
markushi Dec 14, 2022
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Add time-to-initial-display span to Activity transactions ([#2369](https://github.com/getsentry/sentry-java/pull/2369))
- Start a session after init if AutoSessionTracking is enabled ([#2356](https://github.com/getsentry/sentry-java/pull/2356))
- Provide automatic breadcrumbs and transactions for click/scroll events for Compose ([#2390](https://github.com/getsentry/sentry-java/pull/2390))

### Dependencies

Expand Down
4 changes: 0 additions & 4 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun isEnableAutoActivityLifecycleTracing ()Z
public fun isEnableFramesTracking ()Z
public fun isEnableSystemEventBreadcrumbs ()Z
public fun isEnableUserInteractionBreadcrumbs ()Z
public fun isEnableUserInteractionTracing ()Z
public fun setAnrEnabled (Z)V
public fun setAnrReportInDebug (Z)V
public fun setAnrTimeoutIntervalMillis (J)V
Expand All @@ -175,8 +173,6 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableAutoActivityLifecycleTracing (Z)V
public fun setEnableFramesTracking (Z)V
public fun setEnableSystemEventBreadcrumbs (Z)V
public fun setEnableUserInteractionBreadcrumbs (Z)V
public fun setEnableUserInteractionTracing (Z)V
public fun setProfilingTracesHz (I)V
public fun setProfilingTracesIntervalMillis (I)V
}
Expand Down
1 change: 1 addition & 0 deletions sentry-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {
api(projects.sentry)
compileOnly(projects.sentryAndroidFragment)
compileOnly(projects.sentryAndroidTimber)
compileOnly(projects.sentryCompose)

// lifecycle processor, session tracking
implementation(Config.Libs.lifecycleProcess)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@
import io.sentry.SendFireAndForgetOutboxSender;
import io.sentry.SentryLevel;
import io.sentry.android.core.cache.AndroidEnvelopeCache;
import io.sentry.android.core.internal.gestures.AndroidViewGestureTargetLocator;
import io.sentry.android.core.internal.modules.AssetsModulesLoader;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
import io.sentry.compose.gestures.ComposeGestureTargetLocator;
import io.sentry.internal.gestures.GestureTargetLocator;
import io.sentry.transport.NoOpEnvelopeCache;
import io.sentry.util.Objects;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -130,6 +135,25 @@ static void initializeIntegrationsAndProcessors(
options.setTransactionProfiler(
new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector));
options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));

final boolean isAndroidXScrollViewAvailable =
loadClass.isClassAvailable("androidx.core.view.ScrollingView", options);

if (options.getGestureTargetLocators().isEmpty()) {
final List<GestureTargetLocator> gestureTargetLocators = new ArrayList<>(2);
gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable));
try {
gestureTargetLocators.add(new ComposeGestureTargetLocator());
} catch (NoClassDefFoundError error) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"ComposeGestureTargetLocator not available, consider adding the `sentry-compose` library.",
error);
}
options.setGestureTargetLocators(gestureTargetLocators);
}
}

private static void installDefaultIntegrations(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ public final class SentryAndroidOptions extends SentryOptions {
/** Enable or disable automatic breadcrumbs for App Components Using ComponentCallbacks */
private boolean enableAppComponentBreadcrumbs = true;

/** Enable or disable automatic breadcrumbs for User interactions Using Window.Callback */
private boolean enableUserInteractionBreadcrumbs = true;

/**
* Enables the Auto instrumentation for Activity lifecycle tracing.
*
Expand Down Expand Up @@ -93,9 +90,6 @@ public final class SentryAndroidOptions extends SentryOptions {
*/
private int profilingTracesHz = 101;

/** Enables the Auto instrumentation for user interaction tracing. */
private boolean enableUserInteractionTracing = false;

/** Interface that loads the debug images list */
private @NotNull IDebugImagesLoader debugImagesLoader = NoOpDebugImagesLoader.getInstance();

Expand Down Expand Up @@ -241,14 +235,6 @@ public void setEnableAppComponentBreadcrumbs(boolean enableAppComponentBreadcrum
this.enableAppComponentBreadcrumbs = enableAppComponentBreadcrumbs;
}

public boolean isEnableUserInteractionBreadcrumbs() {
return enableUserInteractionBreadcrumbs;
}

public void setEnableUserInteractionBreadcrumbs(boolean enableUserInteractionBreadcrumbs) {
this.enableUserInteractionBreadcrumbs = enableUserInteractionBreadcrumbs;
}

/**
* Enable or disable all the automatic breadcrumbs
*
Expand All @@ -259,7 +245,7 @@ public void enableAllAutoBreadcrumbs(boolean enable) {
enableAppComponentBreadcrumbs = enable;
enableSystemEventBreadcrumbs = enable;
enableAppLifecycleBreadcrumbs = enable;
enableUserInteractionBreadcrumbs = enable;
setEnableUserInteractionBreadcrumbs(enable);
}

/**
Expand Down Expand Up @@ -343,14 +329,6 @@ public void setAttachScreenshot(boolean attachScreenshot) {
this.attachScreenshot = attachScreenshot;
}

public boolean isEnableUserInteractionTracing() {
return enableUserInteractionTracing;
}

public void setEnableUserInteractionTracing(boolean enableUserInteractionTracing) {
this.enableUserInteractionTracing = enableUserInteractionTracing;
}

public boolean isCollectAdditionalContext() {
return collectAdditionalContext;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,12 @@ public final class UserInteractionIntegration
private @Nullable SentryAndroidOptions options;

private final boolean isAndroidXAvailable;
private final boolean isAndroidXScrollViewAvailable;

public UserInteractionIntegration(
final @NotNull Application application, final @NotNull LoadClass classLoader) {
this.application = Objects.requireNonNull(application, "Application is required");

isAndroidXAvailable =
classLoader.isClassAvailable("androidx.core.view.GestureDetectorCompat", options);
isAndroidXScrollViewAvailable =
classLoader.isClassAvailable("androidx.core.view.ScrollingView", options);
}

private void startTracking(final @NotNull Activity activity) {
Expand All @@ -53,7 +49,7 @@ private void startTracking(final @NotNull Activity activity) {
}

final SentryGestureListener gestureListener =
new SentryGestureListener(activity, hub, options, isAndroidXScrollViewAvailable);
new SentryGestureListener(activity, hub, options);
window.setCallback(new SentryWindowCallback(delegate, activity, gestureListener, options));
}
}
Expand Down Expand Up @@ -112,14 +108,14 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) {

this.hub = Objects.requireNonNull(hub, "Hub is required");

final boolean integrationEnabled =
this.options.isEnableUserInteractionBreadcrumbs()
|| this.options.isEnableUserInteractionTracing();
this.options
.getLogger()
.log(
SentryLevel.DEBUG,
"UserInteractionIntegration enabled: %s",
this.options.isEnableUserInteractionBreadcrumbs());
.log(SentryLevel.DEBUG, "UserInteractionIntegration enabled: %s", integrationEnabled);

if (this.options.isEnableUserInteractionBreadcrumbs()) {
if (integrationEnabled) {
if (isAndroidXAvailable) {
application.registerActivityLifecycleCallbacks(this);
this.options.getLogger().log(SentryLevel.DEBUG, "UserInteractionIntegration installed.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.sentry.android.core.internal.gestures;

import android.content.res.Resources;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ScrollView;
import androidx.core.view.ScrollingView;
import io.sentry.internal.gestures.GestureTargetLocator;
import io.sentry.internal.gestures.UiElement;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class AndroidViewGestureTargetLocator implements GestureTargetLocator {

private final boolean isAndroidXAvailable;
private final int[] coordinates = new int[2];

public AndroidViewGestureTargetLocator(final boolean isAndroidXAvailable) {
this.isAndroidXAvailable = isAndroidXAvailable;
}

@Override
public @Nullable UiElement locate(
@NotNull Object root, float x, float y, UiElement.Type targetType) {
if (!(root instanceof View)) {
return null;
}
final View view = (View) root;
if (touchWithinBounds(view, x, y)) {
if (targetType == UiElement.Type.CLICKABLE && isViewTappable(view)) {
return createUiElement(view);
} else if (targetType == UiElement.Type.SCROLLABLE
&& isViewScrollable(view, isAndroidXAvailable)) {
return createUiElement(view);
}
}
return null;
}

private UiElement createUiElement(final @NotNull View targetView) {
try {
final String resourceName = ViewUtils.getResourceId(targetView);
@Nullable String className = targetView.getClass().getCanonicalName();
if (className == null) {
className = targetView.getClass().getSimpleName();
}
return new UiElement(targetView, className, resourceName, null);
} catch (Resources.NotFoundException ignored) {
return null;
}
}

private boolean touchWithinBounds(final @NotNull View view, final float x, final float y) {
view.getLocationOnScreen(coordinates);
int vx = coordinates[0];
int vy = coordinates[1];

int w = view.getWidth();
int h = view.getHeight();

return !(x < vx || x > vx + w || y < vy || y > vy + h);
}

private static boolean isViewTappable(final @NotNull View view) {
return view.isClickable() && view.getVisibility() == View.VISIBLE;
}

private static boolean isViewScrollable(
final @NotNull View view, final boolean isAndroidXAvailable) {
return (isJetpackScrollingView(view, isAndroidXAvailable)
|| AbsListView.class.isAssignableFrom(view.getClass())
|| ScrollView.class.isAssignableFrom(view.getClass()))
&& view.getVisibility() == View.VISIBLE;
}

private static boolean isJetpackScrollingView(
final @NotNull View view, final boolean isAndroidXAvailable) {
if (!isAndroidXAvailable) {
return false;
}
return ScrollingView.class.isAssignableFrom(view.getClass());
}
}
Loading