diff --git a/CHANGELOG.md b/CHANGELOG.md index 51196c33848..12b0db3f13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +### Breaking Changes + +- Remove the deprecated user feedback APIs ([#6337](https://github.com/getsentry/sentry-java/pull/6337)) + - `UserFeedback` and `Sentry.captureUserFeedback(...)`: use `Sentry.feedback().capture(...)` with `io.sentry.protocol.Feedback` instead + - Android: `SentryUserFeedbackDialog`: use `SentryUserFeedbackForm` instead + - Android: the `SentryUserFeedbackButton` view and composable: call `Sentry.feedback().show()` from your own button instead + ### Fixes - Report the number of dropped spans in client reports when a batched span envelope item is discarded, instead of a quantity of 1 (e.g. for Flutter span streaming) ([#6265](https://github.com/getsentry/sentry-java/pull/6265)) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index ae281302482..3e37f1fc398 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -576,33 +576,6 @@ public abstract interface class io/sentry/android/core/SentryShakeDetector$Liste public abstract fun onShake ()V } -public class io/sentry/android/core/SentryUserFeedbackButton : android/widget/Button { - public fun (Landroid/content/Context;)V - public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V - public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V - public fun (Landroid/content/Context;Landroid/util/AttributeSet;II)V - public fun setOnClickListener (Landroid/view/View$OnClickListener;)V -} - -public final class io/sentry/android/core/SentryUserFeedbackDialog : io/sentry/android/core/SentryUserFeedbackForm { -} - -public class io/sentry/android/core/SentryUserFeedbackDialog$Builder : io/sentry/android/core/SentryUserFeedbackForm$Builder { - public fun (Landroid/content/Context;)V - public fun (Landroid/content/Context;I)V - public fun (Landroid/content/Context;ILio/sentry/android/core/SentryUserFeedbackDialog$OptionsConfiguration;)V - public fun (Landroid/content/Context;Lio/sentry/android/core/SentryUserFeedbackDialog$OptionsConfiguration;)V - public fun associatedEventId (Lio/sentry/protocol/SentryId;)Lio/sentry/android/core/SentryUserFeedbackDialog$Builder; - public synthetic fun associatedEventId (Lio/sentry/protocol/SentryId;)Lio/sentry/android/core/SentryUserFeedbackForm$Builder; - public fun configurator (Lio/sentry/SentryFeedbackOptions$OptionsConfigurator;)Lio/sentry/android/core/SentryUserFeedbackDialog$Builder; - public synthetic fun configurator (Lio/sentry/SentryFeedbackOptions$OptionsConfigurator;)Lio/sentry/android/core/SentryUserFeedbackForm$Builder; - public fun create ()Lio/sentry/android/core/SentryUserFeedbackDialog; - public synthetic fun create ()Lio/sentry/android/core/SentryUserFeedbackForm; -} - -public abstract interface class io/sentry/android/core/SentryUserFeedbackDialog$OptionsConfiguration : io/sentry/android/core/SentryUserFeedbackForm$OptionsConfiguration { -} - public class io/sentry/android/core/SentryUserFeedbackForm : android/app/AlertDialog { protected fun onCreate (Landroid/os/Bundle;)V public fun onDetachedFromWindow ()V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackButton.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackButton.java deleted file mode 100644 index 589a9fa2ce6..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackButton.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.sentry.android.core; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Build; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.widget.Button; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @deprecated `SentryUserFeedbackButton` will be removed in the next major version - */ -// temp fix for JDK 21: Constructors call overridable methods; making them final would break the -// public API. -@SuppressWarnings("this-escape") -@Deprecated -public class SentryUserFeedbackButton extends Button { - - private @Nullable OnClickListener delegate; - - @Deprecated - public SentryUserFeedbackButton(Context context) { - super(context); - init(context, null, 0, 0); - } - - @Deprecated - public SentryUserFeedbackButton(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0, 0); - } - - @Deprecated - public SentryUserFeedbackButton(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr, 0); - } - - @Deprecated - public SentryUserFeedbackButton( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - init(context, attrs, defStyleAttr, defStyleRes); - } - - @SuppressLint("SetTextI18n") - @SuppressWarnings("deprecation") - private void init( - final @NotNull Context context, - final @Nullable AttributeSet attrs, - final int defStyleAttr, - final int defStyleRes) { - try (final @NotNull TypedArray typedArray = - context.obtainStyledAttributes( - attrs, R.styleable.SentryUserFeedbackButton, defStyleAttr, defStyleRes)) { - final float dimensionScale = context.getResources().getDisplayMetrics().density; - final float drawablePadding = - typedArray.getDimension(R.styleable.SentryUserFeedbackButton_android_drawablePadding, -1); - final int drawableStart = - typedArray.getResourceId(R.styleable.SentryUserFeedbackButton_android_drawableStart, -1); - final boolean textAllCaps = - typedArray.getBoolean(R.styleable.SentryUserFeedbackButton_android_textAllCaps, false); - final int background = - typedArray.getResourceId(R.styleable.SentryUserFeedbackButton_android_background, -1); - final float padding = - typedArray.getDimension(R.styleable.SentryUserFeedbackButton_android_padding, -1); - final int textColor = - typedArray.getColor(R.styleable.SentryUserFeedbackButton_android_textColor, -1); - final @Nullable String text = - typedArray.getString(R.styleable.SentryUserFeedbackButton_android_text); - - // If the drawable padding is not set, set it to 4dp - if (drawablePadding == -1) { - setCompoundDrawablePadding((int) (4 * dimensionScale)); - } - - // If the drawable start is not set, set it to the default drawable - if (drawableStart == -1) { - setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.sentry_user_feedback_button_logo_24, 0, 0, 0); - } - - // Set the text all caps - setAllCaps(textAllCaps); - - // If the background is not set, set it to the default background - if (background == -1) { - setBackgroundResource(R.drawable.sentry_oval_button_ripple_background); - } - - // If the padding is not set, set it to 12dp - if (padding == -1) { - int defaultPadding = (int) (12 * dimensionScale); - setPadding(defaultPadding, defaultPadding, defaultPadding, defaultPadding); - } - - // If the text color is not set, set it to the default text color - if (textColor == -1) { - // We need the TypedValue to resolve the color from the theme - final @NotNull TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorForeground, typedValue, true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setTextColor(context.getResources().getColor(typedValue.resourceId, context.getTheme())); - } else { - setTextColor(context.getResources().getColor(typedValue.resourceId)); - } - } - - // If the text is not set, set it to "Report a Bug" - if (text == null) { - setText("Report a Bug"); - } - } - - // Set the default ClickListener to open the SentryUserFeedbackForm - setOnClickListener(delegate); - } - - @Override - public void setOnClickListener(final @Nullable OnClickListener listener) { - delegate = listener; - super.setOnClickListener( - v -> { - new SentryUserFeedbackForm.Builder(getContext()).create().show(); - if (delegate != null) { - delegate.onClick(v); - } - }); - } -} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java deleted file mode 100644 index 155464b7b73..00000000000 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryUserFeedbackDialog.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.sentry.android.core; - -import android.content.Context; -import io.sentry.SentryFeedbackOptions; -import io.sentry.protocol.SentryId; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @deprecated Use {@link SentryUserFeedbackForm} instead. - */ -@Deprecated -public final class SentryUserFeedbackDialog extends SentryUserFeedbackForm { - - SentryUserFeedbackDialog( - final @NotNull Context context, - final int themeResId, - final @Nullable SentryId associatedEventId, - final @Nullable SentryUserFeedbackForm.OptionsConfiguration configuration, - final @Nullable SentryFeedbackOptions.OptionsConfigurator configurator) { - super(context, themeResId, associatedEventId, configuration, configurator); - } - - /** - * @deprecated Use {@link SentryUserFeedbackForm.Builder} instead. - */ - @Deprecated - public static class Builder extends SentryUserFeedbackForm.Builder { - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} that uses the default alert dialog - * theme. - * - * @param context the parent context - * @deprecated Use {@link SentryUserFeedbackForm.Builder#Builder(Context)} instead. - */ - @Deprecated - public Builder(final @NotNull Context context) { - super(context); - } - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} that uses an explicit theme - * resource. - * - * @param context the parent context - * @param themeResId the resource ID of the theme - * @deprecated Use {@link SentryUserFeedbackForm.Builder#Builder(Context, int)} instead. - */ - @Deprecated - public Builder(Context context, int themeResId) { - super(context, themeResId); - } - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} with a configuration. - * - * @param context the parent context - * @param configuration the configuration for the feedback options - * @deprecated Use {@link SentryUserFeedbackForm.Builder#Builder(Context, - * SentryUserFeedbackForm.OptionsConfiguration)} instead. - */ - @Deprecated - public Builder( - final @NotNull Context context, final @Nullable OptionsConfiguration configuration) { - super(context, configuration); - } - - /** - * Creates a builder for a {@link SentryUserFeedbackDialog} with a theme and configuration. - * - * @param context the parent context - * @param themeResId the resource ID of the theme - * @param configuration the configuration for the feedback options - * @deprecated Use {@link SentryUserFeedbackForm.Builder#Builder(Context, int, - * SentryUserFeedbackForm.OptionsConfiguration)} instead. - */ - @Deprecated - public Builder( - final @NotNull Context context, - final int themeResId, - final @Nullable OptionsConfiguration configuration) { - super(context, themeResId, configuration); - } - - @Deprecated - @Override - public Builder configurator( - final @Nullable SentryFeedbackOptions.OptionsConfigurator configurator) { - super.configurator(configurator); - return this; - } - - @Deprecated - @Override - public Builder associatedEventId(final @Nullable SentryId associatedEventId) { - super.associatedEventId(associatedEventId); - return this; - } - - @Deprecated - @Override - public SentryUserFeedbackDialog create() { - return new SentryUserFeedbackDialog( - context, themeResId, associatedEventId, configuration, configurator); - } - } - - /** - * @deprecated Use {@link SentryUserFeedbackForm.OptionsConfiguration} instead. - */ - @Deprecated - public interface OptionsConfiguration extends SentryUserFeedbackForm.OptionsConfiguration {} -} diff --git a/sentry-android-core/src/main/res/drawable/sentry_oval_button_ripple_background.xml b/sentry-android-core/src/main/res/drawable/sentry_oval_button_ripple_background.xml deleted file mode 100644 index 10e58ce27f6..00000000000 --- a/sentry-android-core/src/main/res/drawable/sentry_oval_button_ripple_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/sentry-android-core/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml b/sentry-android-core/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml deleted file mode 100644 index 4aab5d6c37b..00000000000 --- a/sentry-android-core/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/sentry-android-core/src/main/res/values/attrs.xml b/sentry-android-core/src/main/res/values/attrs.xml deleted file mode 100644 index efb5651d5da..00000000000 --- a/sentry-android-core/src/main/res/values/attrs.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt index ef920e1d7fe..bbb8783d5fd 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SessionTrackingIntegrationTest.kt @@ -23,7 +23,6 @@ import io.sentry.SentryMetricsEvents import io.sentry.SentryReplayEvent import io.sentry.Session import io.sentry.TraceContext -import io.sentry.UserFeedback import io.sentry.protocol.Feedback import io.sentry.protocol.SentryId import io.sentry.protocol.SentryTransaction @@ -156,10 +155,6 @@ class SessionTrackingIntegrationTest { TODO("Not yet implemented") } - override fun captureUserFeedback(userFeedback: UserFeedback) { - TODO("Not yet implemented") - } - override fun captureSession(session: Session, hint: Hint?) { sessionUpdates.add(session) } diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt index bfcaf2845b3..791006d3713 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt @@ -1,10 +1,7 @@ package io.sentry.uitest.android -import android.graphics.Color import android.view.View import android.widget.EditText -import android.widget.FrameLayout -import android.widget.LinearLayout import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click @@ -27,17 +24,14 @@ import io.sentry.SentryItemType import io.sentry.SentryOptions import io.sentry.android.core.AndroidLogger import io.sentry.android.core.R -import io.sentry.android.core.SentryUserFeedbackButton import io.sentry.android.core.SentryUserFeedbackForm import io.sentry.assertEnvelopeFeedback import io.sentry.protocol.SentryId import io.sentry.protocol.User -import io.sentry.test.getProperty import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull -import kotlin.test.assertNull import kotlin.test.assertTrue import org.hamcrest.Description import org.hamcrest.Matcher @@ -522,97 +516,6 @@ class UserFeedbackUiTest : BaseUiTest() { } } - @Test - fun userFeedbackWidgetDefaults() { - initSentry() - var widgetId = 0 - showWidgetAndCheck { widget -> - widgetId = widget.id - val densityScale = context.resources.displayMetrics.density - assertEquals((densityScale * 4).toInt(), widget.compoundDrawablePadding) - - assertNotNull(widget.compoundDrawables[0]) // Drawable left - assertNull(widget.compoundDrawables[1]) // Drawable top - assertNull(widget.compoundDrawables[2]) // Drawable right - assertNull(widget.compoundDrawables[3]) // Drawable bottom - - // Couldn't find a reliable way to check the drawable, so i'll skip it - - assertFalse(widget.isAllCaps) - - assertEquals( - R.drawable.sentry_oval_button_ripple_background, - widget.getProperty("mBackgroundResource"), - ) - - assertEquals((densityScale * 12).toInt(), widget.paddingStart) - assertEquals((densityScale * 12).toInt(), widget.paddingEnd) - assertEquals((densityScale * 12).toInt(), widget.paddingTop) - assertEquals((densityScale * 12).toInt(), widget.paddingBottom) - - assertEquals("Report a Bug", widget.text) - } - - onView(withId(widgetId)).perform(click()) - // Check that the user feedback dialog is shown - checkViewVisibility(R.id.sentry_dialog_user_feedback_layout) - } - - @Test - fun userFeedbackWidgetDefaultsOverridden() { - initSentry() - showWidgetAndCheck({ widget -> - widget.compoundDrawablePadding = 1 - widget.setCompoundDrawables(null, null, null, null) - widget.isAllCaps = true - widget.setBackgroundResource(R.drawable.sentry_edit_text_border) - widget.setTextColor(Color.RED) - widget.text = "My custom text" - widget.setPadding(0, 0, 0, 0) - }) { widget -> - val densityScale = context.resources.displayMetrics.density - assertEquals(1, widget.compoundDrawablePadding) - - assertNull(widget.compoundDrawables[0]) // Drawable left - assertNull(widget.compoundDrawables[1]) // Drawable top - assertNull(widget.compoundDrawables[2]) // Drawable right - assertNull(widget.compoundDrawables[3]) // Drawable bottom - - assertTrue(widget.isAllCaps) - - assertEquals( - R.drawable.sentry_edit_text_border, - widget.getProperty("mBackgroundResource"), - ) - - assertEquals((densityScale * 0).toInt(), widget.paddingStart) - assertEquals((densityScale * 0).toInt(), widget.paddingEnd) - assertEquals((densityScale * 0).toInt(), widget.paddingTop) - assertEquals((densityScale * 0).toInt(), widget.paddingBottom) - - assertEquals(Color.RED, widget.currentTextColor) - - assertEquals("My custom text", widget.text) - } - } - - @Test - fun userFeedbackWidgetShowsDialogOnClickOverridden() { - initSentry() - var widgetId = 0 - var customListenerCalled = false - showWidgetAndCheck { widget -> - widgetId = widget.id - widget.setOnClickListener { customListenerCalled = true } - } - - onView(withId(widgetId)).perform(click()) - // Check that the user feedback dialog is shown - checkViewVisibility(R.id.sentry_dialog_user_feedback_layout) - // And the custom listener is called, too - assertTrue(customListenerCalled) - } - private fun checkViewVisibility(viewId: Int, isGone: Boolean = false) { onView(withId(viewId)) .check(matches(withEffectiveVisibility(if (isGone) Visibility.GONE else Visibility.VISIBLE))) @@ -655,36 +558,6 @@ class UserFeedbackUiTest : BaseUiTest() { checker(dialog) } - private fun showWidgetAndCheck( - widgetConfig: ((widget: SentryUserFeedbackButton) -> Unit)? = null, - checker: (widget: SentryUserFeedbackButton) -> Unit = {}, - ) { - val buttonId = Int.MAX_VALUE - 1 - val feedbackScenario = launchActivity() - feedbackScenario.onActivity { - val layoutParams = - FrameLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT, - ) - val view = - FrameLayout(it).apply { - addView( - SentryUserFeedbackButton(it).apply { - id = buttonId - widgetConfig?.invoke(this) - }, - layoutParams, - ) - } - it.setContentView(view) - } - checkViewVisibility(buttonId) - onView(withId(buttonId)).check(matches(isDisplayed())).check { view, _ -> - checker(view as SentryUserFeedbackButton) - } - } - fun withError(expectedError: String): Matcher { return object : BoundedMatcher(EditText::class.java) { override fun describeTo(description: Description) { diff --git a/sentry-compose/api/android/sentry-compose.api b/sentry-compose/api/android/sentry-compose.api index 356e507c1aa..c74f96163b3 100644 --- a/sentry-compose/api/android/sentry-compose.api +++ b/sentry-compose/api/android/sentry-compose.api @@ -30,10 +30,6 @@ public final class io/sentry/compose/SentryNavigationIntegrationKt { public static final fun withSentryObservableEffect (Landroidx/navigation/NavHostController;ZZLandroidx/compose/runtime/Composer;II)Landroidx/navigation/NavHostController; } -public final class io/sentry/compose/SentryUserFeedbackButtonKt { - public static final fun SentryUserFeedbackButton (Landroidx/compose/ui/Modifier;Ljava/lang/String;Lio/sentry/SentryFeedbackOptions$OptionsConfigurator;Landroidx/compose/runtime/Composer;II)V -} - public final class io/sentry/compose/gestures/ComposeGestureTargetLocator : io/sentry/internal/gestures/GestureTargetLocator { public static final field $stable I public static final field Companion Lio/sentry/compose/gestures/ComposeGestureTargetLocator$Companion; diff --git a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryUserFeedbackButton.kt b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryUserFeedbackButton.kt deleted file mode 100644 index d82451b79e2..00000000000 --- a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryUserFeedbackButton.kt +++ /dev/null @@ -1,38 +0,0 @@ -package io.sentry.compose - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import io.sentry.Sentry -import io.sentry.SentryFeedbackOptions - -@Deprecated("`SentryUserFeedbackButton` will be removed in the next major version") -@Composable -public fun SentryUserFeedbackButton( - modifier: Modifier = Modifier, - text: String = "Report a Bug", - configurator: SentryFeedbackOptions.OptionsConfigurator? = null, -) { - Button(modifier = modifier, onClick = { Sentry.feedback().show(configurator) }) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Icon( - painter = painterResource(id = R.drawable.sentry_user_feedback_compose_button_logo_24), - contentDescription = null, - ) - Spacer(Modifier.padding(horizontal = 4.dp)) - Text(text = text) - } - } -} diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt index 9660f52ed36..9cfc5b14a78 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MainActivity.kt @@ -775,10 +775,7 @@ fun UserFeedbackScreen() { horizontalArrangement = Arrangement.Center, ) { Icon( - painter = - painterResource( - id = io.sentry.compose.R.drawable.sentry_user_feedback_compose_button_logo_24 - ), + painter = painterResource(id = R.drawable.sentry_user_feedback_button_logo_24), contentDescription = null, ) Spacer(Modifier.padding(horizontal = 4.dp)) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt index 4fd1d74ef7a..e65a1de9eb2 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt @@ -49,9 +49,9 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import coil.compose.AsyncImage +import io.sentry.Sentry import io.sentry.android.replay.sentryReplayUnmask import io.sentry.compose.SentryTraced -import io.sentry.compose.SentryUserFeedbackButton import io.sentry.compose.withSentryObservableEffect import io.sentry.samples.android.GitHubApi import io.sentry.samples.android.R as IR @@ -110,9 +110,16 @@ fun Landing(navigateGithub: () -> Unit, navigateGithubWithArgs: () -> Unit) { } } SentryTraced(tag = "button_dialog") { - SentryUserFeedbackButton(modifier = Modifier.padding(top = 32.dp)) { options -> - options.formTitle = "Report a Bug???" - options.messageLabel = "Please provide details about the bug you encountered." + Button( + onClick = { + Sentry.feedback().show { options -> + options.formTitle = "Report a Bug???" + options.messageLabel = "Please provide details about the bug you encountered." + } + }, + modifier = Modifier.padding(top = 32.dp), + ) { + Text("Report a Bug") } } if (showDialog) { diff --git a/sentry-compose/src/androidMain/res/drawable/sentry_user_feedback_compose_button_logo_24.xml b/sentry-samples/sentry-samples-android/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml similarity index 100% rename from sentry-compose/src/androidMain/res/drawable/sentry_user_feedback_compose_button_logo_24.xml rename to sentry-samples/sentry-samples-android/src/main/res/drawable/sentry_user_feedback_button_logo_24.xml diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 71581076594..1ec1a292dc7 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -729,7 +729,6 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun captureProfileChunk (Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId; public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V public fun clone ()Lio/sentry/IHub; public synthetic fun clone ()Ljava/lang/Object; @@ -806,7 +805,6 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun captureProfileChunk (Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId; public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V public fun clone ()Lio/sentry/IHub; public synthetic fun clone ()Ljava/lang/Object; @@ -1105,7 +1103,6 @@ public abstract interface class io/sentry/IScopes { public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public abstract fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public abstract fun captureUserFeedback (Lio/sentry/UserFeedback;)V public abstract fun clearBreadcrumbs ()V public abstract fun clone ()Lio/sentry/IHub; public abstract fun close ()V @@ -1206,7 +1203,6 @@ public abstract interface class io/sentry/ISentryClient { public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public abstract fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/IScope;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public abstract fun captureUserFeedback (Lio/sentry/UserFeedback;)V public abstract fun close ()V public abstract fun close (Z)V public abstract fun flush (J)V @@ -1728,7 +1724,6 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun captureProfileChunk (Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId; public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V public fun clone ()Lio/sentry/IHub; public synthetic fun clone ()Ljava/lang/Object; @@ -1923,7 +1918,6 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun captureProfileChunk (Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId; public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V public fun clone ()Lio/sentry/IHub; public synthetic fun clone ()Ljava/lang/Object; @@ -2674,7 +2668,6 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun captureProfileChunk (Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId; public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V public fun clone ()Lio/sentry/IHub; public synthetic fun clone ()Ljava/lang/Object; @@ -2754,7 +2747,6 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun captureProfileChunk (Lio/sentry/ProfileChunk;)Lio/sentry/protocol/SentryId; public fun captureReplay (Lio/sentry/SentryReplayEvent;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun clearBreadcrumbs ()V public fun clone ()Lio/sentry/IHub; public synthetic fun clone ()Ljava/lang/Object; @@ -2880,7 +2872,6 @@ public final class io/sentry/Sentry { public static fun captureMessage (Ljava/lang/String;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; public static fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;)Lio/sentry/protocol/SentryId; public static fun captureMessage (Ljava/lang/String;Lio/sentry/SentryLevel;Lio/sentry/ScopeCallback;)Lio/sentry/protocol/SentryId; - public static fun captureUserFeedback (Lio/sentry/UserFeedback;)V public static fun clearBreadcrumbs ()V public static fun close ()V public static fun configureScope (Lio/sentry/ScopeCallback;)V @@ -3134,7 +3125,6 @@ public final class io/sentry/SentryClient : io/sentry/ISentryClient { public fun captureReplayEvent (Lio/sentry/SentryReplayEvent;Lio/sentry/IScope;Lio/sentry/Hint;)Lio/sentry/protocol/SentryId; public fun captureSession (Lio/sentry/Session;Lio/sentry/Hint;)V public fun captureTransaction (Lio/sentry/protocol/SentryTransaction;Lio/sentry/TraceContext;Lio/sentry/IScope;Lio/sentry/Hint;Lio/sentry/ProfilingTraceData;)Lio/sentry/protocol/SentryId; - public fun captureUserFeedback (Lio/sentry/UserFeedback;)V public fun close ()V public fun close (Z)V public fun flush (J)V @@ -3218,7 +3208,6 @@ public final class io/sentry/SentryEnvelopeItem { public static fun fromProfilingTrace (Lio/sentry/ProfilingTraceData;JLio/sentry/ISerializer;)Lio/sentry/SentryEnvelopeItem; public static fun fromReplay (Lio/sentry/ISerializer;Lio/sentry/ILogger;Lio/sentry/SentryReplayEvent;Lio/sentry/ReplayRecording;Z)Lio/sentry/SentryEnvelopeItem; public static fun fromSession (Lio/sentry/ISerializer;Lio/sentry/Session;)Lio/sentry/SentryEnvelopeItem; - public static fun fromUserFeedback (Lio/sentry/ISerializer;Lio/sentry/UserFeedback;)Lio/sentry/SentryEnvelopeItem; public fun getClientReport (Lio/sentry/ISerializer;)Lio/sentry/clientreport/ClientReport; public fun getData ()[B public fun getEvent (Lio/sentry/ISerializer;)Lio/sentry/SentryEvent; @@ -4914,36 +4903,6 @@ public final class io/sentry/UpdateStatus$UpdateError : io/sentry/UpdateStatus { public fun toString ()Ljava/lang/String; } -public final class io/sentry/UserFeedback : io/sentry/JsonSerializable, io/sentry/JsonUnknown { - public fun (Lio/sentry/protocol/SentryId;)V - public fun (Lio/sentry/protocol/SentryId;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public fun getComments ()Ljava/lang/String; - public fun getEmail ()Ljava/lang/String; - public fun getEventId ()Lio/sentry/protocol/SentryId; - public fun getName ()Ljava/lang/String; - public fun getUnknown ()Ljava/util/Map; - public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V - public fun setComments (Ljava/lang/String;)V - public fun setEmail (Ljava/lang/String;)V - public fun setName (Ljava/lang/String;)V - public fun setUnknown (Ljava/util/Map;)V - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/UserFeedback$Deserializer : io/sentry/JsonDeserializer { - public fun ()V - public fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Lio/sentry/UserFeedback; - public synthetic fun deserialize (Lio/sentry/ObjectReader;Lio/sentry/ILogger;)Ljava/lang/Object; -} - -public final class io/sentry/UserFeedback$JsonKeys { - public static final field COMMENTS Ljava/lang/String; - public static final field EMAIL Ljava/lang/String; - public static final field EVENT_ID Ljava/lang/String; - public static final field NAME Ljava/lang/String; - public fun ()V -} - public final class io/sentry/W3CTraceparentHeader { public static final field TRACEPARENT_HEADER Ljava/lang/String; public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Ljava/lang/Boolean;)V diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 5e2d91a9ae8..ac08df95e98 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -86,11 +86,6 @@ public boolean isEnabled() { return Sentry.captureException(throwable, hint, callback); } - @Override - public void captureUserFeedback(@NotNull UserFeedback userFeedback) { - Sentry.captureUserFeedback(userFeedback); - } - @Override public void startSession() { Sentry.startSession(); diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 00395292fd5..017a6f61d40 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -75,11 +75,6 @@ public boolean isEnabled() { return scopes.captureException(throwable, hint, callback); } - @Override - public void captureUserFeedback(@NotNull UserFeedback userFeedback) { - scopes.captureUserFeedback(userFeedback); - } - @Override public void startSession() { scopes.startSession(); diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index 26ea0dcc3ea..be0aa2b9db3 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -213,16 +213,6 @@ SentryId captureException( final @Nullable Hint hint, final @NotNull ScopeCallback callback); - /** - * Captures a manually created user feedback and sends it to Sentry. - * - * @param userFeedback The user feedback to send to Sentry. - * @deprecated Use {@link #feedback()}.{@link IFeedbackApi#capture(io.sentry.protocol.Feedback) - * capture(feedback)} with the new {@link io.sentry.protocol.Feedback} type instead. - */ - @Deprecated - void captureUserFeedback(@NotNull UserFeedback userFeedback); - /** Starts a new session. If there's a running session, it ends it before starting the new one. */ void startSession(); diff --git a/sentry/src/main/java/io/sentry/ISentryClient.java b/sentry/src/main/java/io/sentry/ISentryClient.java index 2a1df15f812..e646a98977b 100644 --- a/sentry/src/main/java/io/sentry/ISentryClient.java +++ b/sentry/src/main/java/io/sentry/ISentryClient.java @@ -170,16 +170,6 @@ public interface ISentryClient { SentryId captureReplayEvent( @NotNull SentryReplayEvent event, @Nullable IScope scope, @Nullable Hint hint); - /** - * Captures a manually created user feedback and sends it to Sentry. - * - * @param userFeedback The user feedback to send to Sentry. - * @deprecated Use {@link IFeedbackApi#capture(io.sentry.protocol.Feedback)} with the new {@link - * io.sentry.protocol.Feedback} type instead. - */ - @Deprecated - void captureUserFeedback(@NotNull UserFeedback userFeedback); - /** * Captures a session. This method transform a session to an envelope and forwards to * captureEnvelope diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index 79a1c72bef3..5fe8af92f4e 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -143,7 +143,6 @@ public JsonSerializer(@NotNull SentryOptions options) { deserializersByClass.put(SpanStatus.class, new SpanStatus.Deserializer()); deserializersByClass.put(User.class, new User.Deserializer()); deserializersByClass.put(Geo.class, new Geo.Deserializer()); - deserializersByClass.put(UserFeedback.class, new UserFeedback.Deserializer()); deserializersByClass.put(ClientReport.class, new ClientReport.Deserializer()); deserializersByClass.put(ViewHierarchyNode.class, new ViewHierarchyNode.Deserializer()); deserializersByClass.put(ViewHierarchy.class, new ViewHierarchy.Deserializer()); diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index d6ec5774e2b..ba68a449a7e 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -81,10 +81,6 @@ public boolean isEnabled() { return SentryId.EMPTY_ID; } - @Deprecated - @Override - public void captureUserFeedback(@NotNull UserFeedback userFeedback) {} - @Override public void startSession() {} diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index b9c650c42a8..811e314d475 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -78,10 +78,6 @@ public boolean isEnabled() { return SentryId.EMPTY_ID; } - @Deprecated - @Override - public void captureUserFeedback(@NotNull UserFeedback userFeedback) {} - @Override public void startSession() {} diff --git a/sentry/src/main/java/io/sentry/NoOpSentryClient.java b/sentry/src/main/java/io/sentry/NoOpSentryClient.java index ac9ff2344bf..6a3d5d3b84e 100644 --- a/sentry/src/main/java/io/sentry/NoOpSentryClient.java +++ b/sentry/src/main/java/io/sentry/NoOpSentryClient.java @@ -44,10 +44,6 @@ public void flush(long timeoutMillis) {} return SentryId.EMPTY_ID; } - @Deprecated - @Override - public void captureUserFeedback(@NotNull UserFeedback userFeedback) {} - @Override public void captureSession(@NotNull Session session, @Nullable Hint hint) {} diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index d3926d56993..caea6fe9600 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -349,29 +349,6 @@ private void assignTraceContext(final @NotNull SentryEvent event) { return sentryId; } - @Deprecated - @Override - public void captureUserFeedback(final @NotNull UserFeedback userFeedback) { - if (!isEnabled()) { - getOptions() - .getLogger() - .log( - SentryLevel.WARNING, - "Instance is disabled and this 'captureUserFeedback' call is a no-op."); - } else { - try { - getClient().captureUserFeedback(userFeedback); - } catch (Throwable e) { - getOptions() - .getLogger() - .log( - SentryLevel.ERROR, - "Error while capturing captureUserFeedback: " + userFeedback.toString(), - e); - } - } - } - @Override public void startSession() { if (!isEnabled()) { diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index b697a950501..32facd77c52 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -82,12 +82,6 @@ public boolean isEnabled() { return Sentry.captureException(throwable, hint, callback); } - @Deprecated - @Override - public void captureUserFeedback(@NotNull UserFeedback userFeedback) { - Sentry.captureUserFeedback(userFeedback); - } - @Override public void startSession() { Sentry.startSession(); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 03288ae00ad..89ccb97058d 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -927,19 +927,6 @@ public static void close() { return getCurrentScopes().captureException(throwable, hint, callback); } - /** - * Captures a manually created user feedback and sends it to Sentry. - * - * @param userFeedback The user feedback to send to Sentry. - * @deprecated Use {@link #feedback()}.{@link IFeedbackApi#capture(Feedback) capture(feedback)} - * with the new {@link Feedback} type instead. - */ - @Deprecated - @SuppressWarnings("InlineMeSuggester") - public static void captureUserFeedback(final @NotNull UserFeedback userFeedback) { - getCurrentScopes().captureUserFeedback(userFeedback); - } - /** * Adds a breadcrumb to the current Scope * diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index c0758a4efd4..9c019df1f58 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -769,47 +769,6 @@ private SentryEvent processFeedbackEvent( return feedbackEvent; } - @Deprecated - @Override - public void captureUserFeedback(final @NotNull UserFeedback userFeedback) { - Objects.requireNonNull(userFeedback, "SentryEvent is required."); - - if (SentryId.EMPTY_ID.equals(userFeedback.getEventId())) { - options.getLogger().log(SentryLevel.WARNING, "Capturing userFeedback without a Sentry Id."); - return; - } - options - .getLogger() - .log(SentryLevel.DEBUG, "Capturing userFeedback: %s", userFeedback.getEventId()); - - try { - final @NotNull SentryEnvelope envelope = buildEnvelope(userFeedback); - sendEnvelope(envelope, null); - } catch (IOException e) { - options - .getLogger() - .log( - SentryLevel.WARNING, - e, - "Capturing user feedback %s failed.", - userFeedback.getEventId()); - } - } - - @Deprecated - private @NotNull SentryEnvelope buildEnvelope(final @NotNull UserFeedback userFeedback) { - final List envelopeItems = new ArrayList<>(); - - final SentryEnvelopeItem userFeedbackItem = - SentryEnvelopeItem.fromUserFeedback(options.getSerializer(), userFeedback); - envelopeItems.add(userFeedbackItem); - - final SentryEnvelopeHeader envelopeHeader = - new SentryEnvelopeHeader(userFeedback.getEventId(), options.getSdkVersion()); - - return new SentryEnvelope(envelopeHeader, envelopeItems); - } - private @NotNull SentryEnvelope buildEnvelope( final @NotNull CheckIn checkIn, final @Nullable TraceContext traceContext) { final List envelopeItems = new ArrayList<>(); diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index d1858c02767..3d1552a523d 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -176,36 +176,6 @@ public final class SentryEnvelopeItem { } } - @Deprecated - public static SentryEnvelopeItem fromUserFeedback( - final @NotNull ISerializer serializer, final @NotNull UserFeedback userFeedback) { - Objects.requireNonNull(serializer, "ISerializer is required."); - Objects.requireNonNull(userFeedback, "UserFeedback is required."); - - final CachedItem cachedItem = - new CachedItem( - () -> { - try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = - new BufferedWriter( - new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { - serializer.serialize(userFeedback, writer); - return stream.toByteArray(); - } - }); - - SentryEnvelopeItemHeader itemHeader = - new SentryEnvelopeItemHeader( - SentryItemType.UserFeedback, - () -> cachedItem.getBytes().length, - "application/json", - null); - - // avoid method refs on Android due to some issues with older AGP setups - // noinspection Convert2MethodRef - return new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes()); - } - public static SentryEnvelopeItem fromCheckIn( final @NotNull ISerializer serializer, final @NotNull CheckIn checkIn) { Objects.requireNonNull(serializer, "ISerializer is required."); diff --git a/sentry/src/main/java/io/sentry/UserFeedback.java b/sentry/src/main/java/io/sentry/UserFeedback.java deleted file mode 100644 index b9d0ade0f9d..00000000000 --- a/sentry/src/main/java/io/sentry/UserFeedback.java +++ /dev/null @@ -1,229 +0,0 @@ -package io.sentry; - -import io.sentry.protocol.SentryId; -import io.sentry.vendor.gson.stream.JsonToken; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Adds additional information about what happened to an event. - * - * @deprecated Use {@link io.sentry.protocol.Feedback} with {@link Sentry#feedback()}.{@link - * IFeedbackApi#capture(io.sentry.protocol.Feedback) capture(feedback)} instead. - */ -@Deprecated -public final class UserFeedback implements JsonUnknown, JsonSerializable { - - private final SentryId eventId; - private @Nullable String name; - private @Nullable String email; - private @Nullable String comments; - - private @Nullable Map unknown; - - /** - * Initializes SentryUserFeedback and sets the required eventId. - * - * @param eventId The eventId of the event to which the user feedback is associated. - */ - public UserFeedback(SentryId eventId) { - this(eventId, null, null, null); - } - - /** - * Initializes SentryUserFeedback and sets the required eventId. - * - * @param eventId The eventId of the event to which the user feedback is associated. - * @param name the name of the user. - * @param email the email of the user. - * @param comments comments of the user about what happened. - */ - public UserFeedback( - SentryId eventId, @Nullable String name, @Nullable String email, @Nullable String comments) { - this.eventId = eventId; - this.name = name; - this.email = email; - this.comments = comments; - } - - /** - * Gets the eventId of the event to which the user feedback is associated. - * - * @return the eventId - */ - public SentryId getEventId() { - return eventId; - } - - /** - * Gets the name of the user. - * - * @return the name. - */ - public @Nullable String getName() { - return name; - } - - /** - * Sets the name of the user. - * - * @param name the name of the user. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Gets the email of the user. - * - * @return the email. - */ - public @Nullable String getEmail() { - return email; - } - - /** - * Sets the email of the user. - * - * @param email the email of the user. - */ - public void setEmail(@Nullable String email) { - this.email = email; - } - - /** - * Gets comments of the user about what happened. - * - * @return the comments - */ - public @Nullable String getComments() { - return comments; - } - - /** - * Sets comments of the user about what happened. - * - * @param comments the comments - */ - public void setComments(@Nullable String comments) { - this.comments = comments; - } - - @Override - public String toString() { - return "UserFeedback{" - + "eventId=" - + eventId - + ", name='" - + name - + '\'' - + ", email='" - + email - + '\'' - + ", comments='" - + comments - + '\'' - + '}'; - } - - // JsonKeys - - public static final class JsonKeys { - public static final String EVENT_ID = "event_id"; - public static final String NAME = "name"; - public static final String EMAIL = "email"; - public static final String COMMENTS = "comments"; - } - - // JsonUnknown - - @Override - public @Nullable Map getUnknown() { - return unknown; - } - - @Override - public void setUnknown(@Nullable Map unknown) { - this.unknown = unknown; - } - - // JsonSerializable - - @Override - public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger) - throws IOException { - writer.beginObject(); - writer.name(JsonKeys.EVENT_ID); - eventId.serialize(writer, logger); - if (name != null) { - writer.name(JsonKeys.NAME).value(name); - } - if (email != null) { - writer.name(JsonKeys.EMAIL).value(email); - } - if (comments != null) { - writer.name(JsonKeys.COMMENTS).value(comments); - } - if (unknown != null) { - for (String key : unknown.keySet()) { - Object value = unknown.get(key); - writer.name(key).value(logger, value); - } - } - writer.endObject(); - } - - // JsonDeserializer - - public static final class Deserializer implements JsonDeserializer { - @Override - public @NotNull UserFeedback deserialize(@NotNull ObjectReader reader, @NotNull ILogger logger) - throws Exception { - SentryId sentryId = null; - String name = null; - String email = null; - String comments = null; - Map unknown = null; - - reader.beginObject(); - while (reader.peek() == JsonToken.NAME) { - final String nextName = reader.nextName(); - switch (nextName) { - case JsonKeys.EVENT_ID: - sentryId = new SentryId.Deserializer().deserialize(reader, logger); - break; - case JsonKeys.NAME: - name = reader.nextStringOrNull(); - break; - case JsonKeys.EMAIL: - email = reader.nextStringOrNull(); - break; - case JsonKeys.COMMENTS: - comments = reader.nextStringOrNull(); - break; - default: - if (unknown == null) { - unknown = new HashMap<>(); - } - reader.nextUnknown(logger, unknown, nextName); - break; - } - } - reader.endObject(); - - if (sentryId == null) { - String message = "Missing required field \"" + JsonKeys.EVENT_ID + "\""; - Exception exception = new IllegalStateException(message); - logger.log(SentryLevel.ERROR, message, exception); - throw exception; - } - - UserFeedback userFeedback = new UserFeedback(sentryId, name, email, comments); - userFeedback.setUnknown(unknown); - return userFeedback; - } - } -} diff --git a/sentry/src/test/java/io/sentry/HubAdapterTest.kt b/sentry/src/test/java/io/sentry/HubAdapterTest.kt index 0dbb6a43c11..4a51a5b1496 100644 --- a/sentry/src/test/java/io/sentry/HubAdapterTest.kt +++ b/sentry/src/test/java/io/sentry/HubAdapterTest.kt @@ -95,13 +95,6 @@ class HubAdapterTest { verify(scopes).captureException(eq(throwable), eq(hint), eq(scopeCallback)) } - @Test - fun `captureUserFeedback calls Hub`() { - val userFeedback = mock() - HubAdapter.getInstance().captureUserFeedback(userFeedback) - verify(scopes).captureUserFeedback(eq(userFeedback)) - } - @Test fun `captureCheckIn calls Hub`() { val checkIn = mock() diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index 95b664e13a9..285351828f8 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1404,31 +1404,6 @@ class JsonSerializerTest { assertEquals("test", deserialized?.measurements?.get("test_measurement")?.unit) } - @Test - fun `serializing user feedback`() { - val actual = serializeToString(userFeedback) - - val expected = - "{\"event_id\":\"${userFeedback.eventId}\",\"name\":\"${userFeedback.name}\"," + - "\"email\":\"${userFeedback.email}\",\"comments\":\"${userFeedback.comments}\"}" - - assertEquals(expected, actual) - } - - @Test - fun `deserializing user feedback`() { - val jsonUserFeedback = - "{\"event_id\":\"c2fb8fee2e2b49758bcb67cda0f713c7\"," + - "\"name\":\"John\",\"email\":\"john@me.com\",\"comments\":\"comment\"}" - val actual = - fixture.serializer.deserialize(StringReader(jsonUserFeedback), UserFeedback::class.java) - assertNotNull(actual) - assertEquals(userFeedback.eventId, actual.eventId) - assertEquals(userFeedback.name, actual.name) - assertEquals(userFeedback.email, actual.email) - assertEquals(userFeedback.comments, actual.comments) - } - @Test fun `serialize envelope with item throwing`() { val eventID = SentryId() @@ -1744,16 +1719,6 @@ class JsonSerializerTest { "anr_foreground", ) - private val userFeedback: UserFeedback - get() { - val eventId = SentryId("c2fb8fee2e2b49758bcb67cda0f713c7") - return UserFeedback(eventId).apply { - name = "John" - email = "john@me.com" - comments = "comment" - } - } - private val appStartProfilingOptions = SentryAppStartProfilingOptions().apply { traceSampled = false diff --git a/sentry/src/test/java/io/sentry/JsonUnknownSerializationTest.kt b/sentry/src/test/java/io/sentry/JsonUnknownSerializationTest.kt index 37bf1efef98..6231bd2ecfb 100644 --- a/sentry/src/test/java/io/sentry/JsonUnknownSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/JsonUnknownSerializationTest.kt @@ -16,7 +16,6 @@ import io.sentry.protocol.SdkVersion import io.sentry.protocol.SentryEnvelopeHeaderSerializationTest import io.sentry.protocol.SentryEnvelopeItemHeaderSerializationTest import io.sentry.protocol.SentryException -import io.sentry.protocol.SentryId import io.sentry.protocol.SentryPackage import io.sentry.protocol.SentryRuntime import io.sentry.protocol.SentrySpan @@ -91,8 +90,6 @@ class JsonUnknownSerializationTest( val spanContext = givenJsonUnknown(SpanContext("c2fb8fee2e2b49758bcb67cda0f713c7")) val traceContext = givenJsonUnknown(TraceContextSerializationTest.Fixture().getSut()) val user = givenJsonUnknown(User()) - val userFeedback = - givenJsonUnknown(UserFeedback(SentryId("c2fb8fee2e2b49758bcb67cda0f713c7"))) val geo = givenJsonUnknown(Geo()) // Same instance for first and second param, so we cann access both JsonUnknown and @@ -140,7 +137,6 @@ class JsonUnknownSerializationTest( arrayOf(spanContext, spanContext, SpanContext.Deserializer()::deserialize), arrayOf(traceContext, traceContext, TraceContext.Deserializer()::deserialize), arrayOf(user, user, User.Deserializer()::deserialize), - arrayOf(userFeedback, userFeedback, UserFeedback.Deserializer()::deserialize), arrayOf(geo, geo, Geo.Deserializer()::deserialize), ) } diff --git a/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt b/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt index 1de22cfd3c3..8e22d98cf74 100644 --- a/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesAdapterTest.kt @@ -95,13 +95,6 @@ class ScopesAdapterTest { verify(scopes).captureException(eq(throwable), eq(hint), eq(scopeCallback)) } - @Test - fun `captureUserFeedback calls Scopes`() { - val userFeedback = mock() - ScopesAdapter.getInstance().captureUserFeedback(userFeedback) - verify(scopes).captureUserFeedback(eq(userFeedback)) - } - @Test fun `captureCheckIn calls Scopes`() { val checkIn = mock() diff --git a/sentry/src/test/java/io/sentry/ScopesTest.kt b/sentry/src/test/java/io/sentry/ScopesTest.kt index a90cd47f0bc..c7c5f93f4d9 100644 --- a/sentry/src/test/java/io/sentry/ScopesTest.kt +++ b/sentry/src/test/java/io/sentry/ScopesTest.kt @@ -787,54 +787,6 @@ class ScopesTest { // endregion - // region captureUserFeedback tests - - @Test - fun `when captureUserFeedback is called it is forwarded to the client`() { - val (sut, mockClient) = getEnabledScopes() - sut.captureUserFeedback(userFeedback) - - verify(mockClient) - .captureUserFeedback( - check { - assertEquals(userFeedback.eventId, it.eventId) - assertEquals(userFeedback.email, it.email) - assertEquals(userFeedback.name, it.name) - assertEquals(userFeedback.comments, it.comments) - } - ) - } - - @Test - fun `when captureUserFeedback is called on disabled client, do nothing`() { - val (sut, mockClient) = getEnabledScopes() - sut.close() - - sut.captureUserFeedback(userFeedback) - verify(mockClient, never()).captureUserFeedback(any()) - } - - @Test - fun `when captureUserFeedback is called and client throws, don't crash`() { - val (sut, mockClient) = getEnabledScopes() - - whenever(mockClient.captureUserFeedback(any())).doThrow(IllegalArgumentException("")) - - sut.captureUserFeedback(userFeedback) - } - - private val userFeedback: UserFeedback - get() { - val eventId = SentryId("c2fb8fee2e2b49758bcb67cda0f713c7") - return UserFeedback(eventId).apply { - name = "John" - email = "john@me.com" - comments = "comment" - } - } - - // endregion - // region captureCheckIn tests @Test diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index 6db8ca86521..926cf2d2884 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -1022,66 +1022,6 @@ class SentryClientTest { verifyAttachmentsInEnvelope(event.eventId) } - @Test - fun `when captureUserFeedback with empty id, envelope is not sent`() { - val sut = fixture.getSut() - - sut.captureUserFeedback(UserFeedback(SentryId.EMPTY_ID)) - - verify(fixture.transport, never()).send(any(), anyOrNull()) - } - - @Test - fun `when captureUserFeedback, envelope is sent`() { - val sut = fixture.getSut() - - sut.captureUserFeedback(userFeedback) - - verify(fixture.transport) - .send( - check { actual -> - assertEquals(userFeedback.eventId, actual.header.eventId) - assertEquals(fixture.sentryOptions.sdkVersion, actual.header.sdkVersion) - - assertEquals(1, actual.items.count()) - val item = actual.items.first() - assertEquals(SentryItemType.UserFeedback, item.header.type) - assertEquals("application/json", item.header.contentType) - - assertEnvelopeItemDataForUserFeedback(item) - } - ) - } - - private fun assertEnvelopeItemDataForUserFeedback(item: SentryEnvelopeItem) { - val stream = ByteArrayOutputStream() - val writer = stream.bufferedWriter(Charset.forName("UTF-8")) - fixture.sentryOptions.serializer.serialize(userFeedback, writer) - val expectedData = stream.toByteArray() - assertTrue(Arrays.equals(expectedData, item.data)) - } - - @Test - fun `when captureUserFeedback and connection throws, log exception`() { - val sut = fixture.getSut() - - val exception = IOException("No connection") - whenever(fixture.transport.send(any())).thenThrow(exception) - - val logger = mock() - fixture.sentryOptions.setLogger(logger) - - sut.captureUserFeedback(userFeedback) - - verify(logger) - .log( - SentryLevel.WARNING, - exception, - "Capturing user feedback %s failed.", - userFeedback.eventId, - ) - } - @Test fun `when captureCheckIn, envelope is sent`() { val sut = fixture.getSut() @@ -4517,19 +4457,6 @@ class SentryClientTest { return Session("dis", User(), "env", release) } - private val userFeedback: UserFeedback - get() { - val eventId = SentryId("c2fb8fee2e2b49758bcb67cda0f713c7") - val userFeedback = UserFeedback(eventId) - userFeedback.apply { - name = "John" - email = "john@me.com" - comments = "comment" - } - - return userFeedback - } - private val checkIn = CheckIn("some_slug", CheckInStatus.OK) internal class CustomTransportGate : ITransportGate { diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index 6d6212e487b..22cd03244bc 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -365,19 +365,6 @@ class SentryTest { ) } - @Test - fun `captureUserFeedback gets forwarded to client`() { - initForTest { it.dsn = dsn } - - val client = createSentryClientMock() - Sentry.getCurrentScopes().bindClient(client) - - val userFeedback = UserFeedback(SentryId.EMPTY_ID) - Sentry.captureUserFeedback(userFeedback) - - verify(client).captureUserFeedback(argThat { eventId == userFeedback.eventId }) - } - @Test fun `startTransaction sets operation and description`() { initForTest { diff --git a/sentry/src/test/java/io/sentry/UserFeedbackSerializationTest.kt b/sentry/src/test/java/io/sentry/UserFeedbackSerializationTest.kt deleted file mode 100644 index b46f8d2688b..00000000000 --- a/sentry/src/test/java/io/sentry/UserFeedbackSerializationTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package io.sentry - -import io.sentry.protocol.SentryId -import java.io.StringReader -import java.io.StringWriter -import java.lang.Exception -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.fail -import org.junit.Test -import org.mockito.kotlin.any -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify - -class UserFeedbackSerializationTest { - private class Fixture { - val logger = mock() - - fun getSut(): UserFeedback { - val eventId = SentryId("c2fb8fee2e2b49758bcb67cda0f713c7") - return UserFeedback(eventId).apply { - name = "John" - email = "john@me.com" - comments = "comment" - } - } - } - - private val fixture = Fixture() - - @Test - fun serialize() { - val userFeedback = fixture.getSut() - val actual = serialize(userFeedback) - - val expected = - "{\"event_id\":\"${userFeedback.eventId}\",\"name\":\"${userFeedback.name}\"," + - "\"email\":\"${userFeedback.email}\",\"comments\":\"${userFeedback.comments}\"}" - - assertEquals(expected, actual) - } - - @Test - fun deserialize() { - val userFeedback = fixture.getSut() - val jsonUserFeedback = - "{\"event_id\":\"c2fb8fee2e2b49758bcb67cda0f713c7\"," + - "\"name\":\"John\",\"email\":\"john@me.com\",\"comments\":\"comment\"}" - val reader = JsonObjectReader(StringReader(jsonUserFeedback)) - val actual = UserFeedback.Deserializer().deserialize(reader, fixture.logger) - assertNotNull(actual) - assertEquals(userFeedback.eventId, actual.eventId) - assertEquals(userFeedback.name, actual.name) - assertEquals(userFeedback.email, actual.email) - assertEquals(userFeedback.comments, actual.comments) - } - - @Test - fun `deserializing user feedback with missing required fields`() { - val jsonUserFeedbackWithoutEventId = - "{\"name\":\"John\",\"email\":\"john@me.com\"," + "\"comments\":\"comment\"}" - val reader = JsonObjectReader(StringReader(jsonUserFeedbackWithoutEventId)) - - try { - UserFeedback.Deserializer().deserialize(reader, fixture.logger) - fail() - } catch (exception: Exception) { - verify(fixture.logger).log(eq(SentryLevel.ERROR), any(), any()) - } - } - - // Helper - - private fun serialize(jsonSerializable: JsonSerializable): String { - val wrt = StringWriter() - val jsonWrt = JsonObjectWriter(wrt, 100) - jsonSerializable.serialize(jsonWrt, fixture.logger) - return wrt.toString() - } -} diff --git a/sentry/src/test/java/io/sentry/UserReportEnvelopeItem.kt b/sentry/src/test/java/io/sentry/UserReportEnvelopeItem.kt new file mode 100644 index 00000000000..f563b63b7d5 --- /dev/null +++ b/sentry/src/test/java/io/sentry/UserReportEnvelopeItem.kt @@ -0,0 +1,22 @@ +package io.sentry + +import io.sentry.protocol.SentryId + +/** + * The SDK no longer creates `user_report` items, but hybrid SDKs and envelopes cached by older + * versions can still send them through rate limiting and client reports. + */ +fun userReportEnvelopeItem(): SentryEnvelopeItem { + val data = "{\"event_id\":\"${SentryId()}\"}".toByteArray() + val header = + SentryEnvelopeItemHeader( + SentryItemType.UserFeedback, + data.size, + "application/json", + null, + null, + null, + null, + ) + return SentryEnvelopeItem(header, data) +} diff --git a/sentry/src/test/java/io/sentry/clientreport/ClientReportTest.kt b/sentry/src/test/java/io/sentry/clientreport/ClientReportTest.kt index e077b25fb3c..3408198b657 100644 --- a/sentry/src/test/java/io/sentry/clientreport/ClientReportTest.kt +++ b/sentry/src/test/java/io/sentry/clientreport/ClientReportTest.kt @@ -32,7 +32,6 @@ import io.sentry.Session import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext import io.sentry.UncaughtExceptionHandlerIntegration.UncaughtExceptionHint -import io.sentry.UserFeedback import io.sentry.dsnString import io.sentry.hints.Retryable import io.sentry.protocol.Feedback @@ -40,6 +39,7 @@ import io.sentry.protocol.SentryId import io.sentry.protocol.SentryTransaction import io.sentry.protocol.User import io.sentry.test.initForTest +import io.sentry.userReportEnvelopeItem import io.sentry.util.HintUtils import java.io.File import java.time.LocalDateTime @@ -95,10 +95,7 @@ class ClientReportTest { SentryEnvelopeItem.fromEvent(opts.serializer, SentryTransaction(transaction)), SentryEnvelopeItem.fromEvent(opts.serializer, SentryEvent()), SentryEnvelopeItem.fromSession(opts.serializer, Session("dis", User(), "env", "0.0.1")), - SentryEnvelopeItem.fromUserFeedback( - opts.serializer, - UserFeedback(SentryId(UUID.randomUUID())), - ), + userReportEnvelopeItem(), SentryEnvelopeItem.fromAttachment( opts.serializer, NoOpLogger.getInstance(), diff --git a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt index 00ba9cd9872..721c8202a83 100644 --- a/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt +++ b/sentry/src/test/java/io/sentry/transport/RateLimiterTest.kt @@ -29,7 +29,6 @@ import io.sentry.SentryReplayEvent import io.sentry.SentryTracer import io.sentry.Session import io.sentry.TransactionContext -import io.sentry.UserFeedback import io.sentry.clientreport.DiscardReason import io.sentry.clientreport.IClientReportRecorder import io.sentry.hints.DiskFlushNotification @@ -40,9 +39,9 @@ import io.sentry.protocol.User import io.sentry.test.DeferredExecutorService import io.sentry.test.getProperty import io.sentry.time.TestMonotonicTicker +import io.sentry.userReportEnvelopeItem import io.sentry.util.HintUtils import java.io.File -import java.util.UUID import java.util.concurrent.Future import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.SECONDS @@ -259,15 +258,7 @@ class RateLimiterTest { val rateLimiter = fixture.getSUT() val eventItem = SentryEnvelopeItem.fromEvent(fixture.serializer, SentryEvent()) - val userFeedbackItem = - SentryEnvelopeItem.fromUserFeedback( - fixture.serializer, - UserFeedback(SentryId(UUID.randomUUID())).also { - it.comments = "It broke on Android. I don't know why, but this happens." - it.email = "john@me.com" - it.setName("John Me") - }, - ) + val userFeedbackItem = userReportEnvelopeItem() val scopes = mock() whenever(scopes.options).thenReturn(SentryOptions()) val transaction = SentryTracer(TransactionContext("name", "op"), scopes) @@ -342,15 +333,7 @@ class RateLimiterTest { whenever(scopes.options).thenReturn(SentryOptions()) val eventItem = SentryEnvelopeItem.fromEvent(fixture.serializer, SentryEvent()) - val userFeedbackItem = - SentryEnvelopeItem.fromUserFeedback( - fixture.serializer, - UserFeedback(SentryId(UUID.randomUUID())).also { - it.comments = "It broke on Android. I don't know why, but this happens." - it.email = "john@me.com" - it.setName("John Me") - }, - ) + val userFeedbackItem = userReportEnvelopeItem() val transaction = SentryTracer(TransactionContext("name", "op"), scopes) val profileItem = SentryEnvelopeItem.fromProfilingTrace(