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 19 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Provide automatic breadcrumbs and transactions for click/scroll events for Compose ([#2390](https://github.com/getsentry/sentry-java/pull/2390))

## 6.9.2

### Fixes
Expand Down
4 changes: 0 additions & 4 deletions sentry-android-core/api/sentry-android-core.api
Expand Up @@ -159,8 +159,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 @@ -174,8 +172,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
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
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,21 @@ 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);
final boolean isComposeGestureTargetLocatorAvailable =
loadClass.isClassAvailable(
"io.sentry.compose.gestures.ComposeGestureTargetLocator", options.getLogger());

if (options.getGestureTargetLocators().isEmpty()) {
final List<GestureTargetLocator> gestureTargetLocators = new ArrayList<>(2);
gestureTargetLocators.add(new AndroidViewGestureTargetLocator(isAndroidXScrollViewAvailable));
if (isComposeGestureTargetLocatorAvailable) {
gestureTargetLocators.add(new ComposeGestureTargetLocator(options));
}
options.setGestureTargetLocators(gestureTargetLocators);
}
}

private static void installDefaultIntegrations(
Expand Down
Expand Up @@ -5,7 +5,10 @@
import io.sentry.Sentry;
import io.sentry.SentryOptions;
import io.sentry.SpanStatus;
import io.sentry.internal.gestures.GestureTargetLocator;
import io.sentry.protocol.SdkVersion;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
Expand Down Expand Up @@ -39,9 +42,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 +93,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 +238,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 +248,7 @@ public void enableAllAutoBreadcrumbs(boolean enable) {
enableAppComponentBreadcrumbs = enable;
enableSystemEventBreadcrumbs = enable;
enableAppLifecycleBreadcrumbs = enable;
enableUserInteractionBreadcrumbs = enable;
setEnableUserInteractionBreadcrumbs(enable);
}

/**
Expand Down Expand Up @@ -343,14 +332,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
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
@@ -0,0 +1,80 @@
package io.sentry.android.core.internal.gestures;

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;
}

static boolean isViewTappable(final @NotNull View view) {
markushi marked this conversation as resolved.
Show resolved Hide resolved
return view.isClickable() && view.getVisibility() == View.VISIBLE;
}

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());
}

static boolean touchWithinBounds(
markushi marked this conversation as resolved.
Show resolved Hide resolved
final @NotNull View view, final float x, final float y, final int[] coords) {
view.getLocationOnScreen(coords);
int vx = coords[0];
int vy = coords[1];

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

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

@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, coordinates)) {
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(@NotNull View targetView) {
markushi marked this conversation as resolved.
Show resolved Hide resolved
final String resourceName = ViewUtils.getResourceIdWithFallback(targetView);
@Nullable String className = targetView.getClass().getCanonicalName();
if (className == null) {
className = targetView.getClass().getSimpleName();
}
return new UiElement(targetView, className, resourceName, null);
}
}