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

feat: Feedback Widget for React Native #4435

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bda351c
(1) feat: Add Feedback Form Component (#4328)
antonis Jan 10, 2025
078cb37
Merge branch 'main' into feedback-ui
antonis Jan 10, 2025
7add44c
Merge branch 'main' into feedback-ui
antonis Jan 14, 2025
8709a48
Update changelog PR reference
antonis Jan 14, 2025
08eecba
test: Adds snapshot tests (#4379)
antonis Jan 15, 2025
4529b68
feat: handle `captureFeedback` errors (#4364)
antonis Jan 15, 2025
48ff52e
Merge branch 'main' into feedback-ui
antonis Jan 15, 2025
93b770e
Merge branch 'main' into feedback-ui
antonis Jan 16, 2025
aa15c88
(2.4) feat(feedback-ui): Add screenshots (#4338)
antonis Jan 16, 2025
312116d
Merge branch 'main' into feedback-ui
antonis Jan 20, 2025
f3c3563
Merge branch 'main' into feedback-ui
antonis Jan 22, 2025
b7b36d8
Merge branch 'main' into feedback-ui
antonis Jan 27, 2025
eda1cb7
Autoinject feedback widget (#4483)
antonis Jan 29, 2025
74748f8
Adds feedbackIntegration for configuring the feedback form (#4485)
antonis Jan 30, 2025
df77091
Merge branch 'main' into feedback-ui
antonis Jan 30, 2025
dbdfceb
Merge branch 'main' into feedback-ui
antonis Jan 31, 2025
f8988bc
Merge branch 'main' into feedback-ui
antonis Feb 3, 2025
03ece25
Merge branch 'main' into feedback-ui
antonis Feb 7, 2025
07b3f54
Merge branch 'main' into feedback-ui
antonis Feb 10, 2025
7ec9441
Feedback modal UI tweaks (#4492)
antonis Feb 11, 2025
fe99425
Merge branch 'main' into feedback-ui
antonis Feb 11, 2025
22cde46
Fix changelog
antonis Feb 11, 2025
e17ab11
Feedback UI: Use Image Picker libraries from integrations (#4524)
antonis Feb 14, 2025
fcbc6c6
Merge branch 'main' into feedback-ui
antonis Feb 14, 2025
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@

## Unreleased

### Features

- User Feedback Form Component Beta ([#4435](https://github.com/getsentry/sentry-react-native/pull/4435))

To collect user feedback from inside your application call `Sentry.showFeedbackForm()` or add the `FeedbackForm` component.

```jsx
import { FeedbackForm } from "@sentry/react-native";
...
<FeedbackForm/>
```

### Fixes

- Various crashes and issues of Session Replay on Android. See the Android SDK version bump for more details. ([#4529](https://github.com/getsentry/sentry-react-native/pull/4529))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.util.SparseIntArray;
import androidx.core.app.FrameMetricsAggregator;
import androidx.fragment.app.FragmentActivity;
Expand Down Expand Up @@ -72,6 +73,7 @@
import io.sentry.vendor.Base64;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
Expand Down Expand Up @@ -970,6 +972,39 @@ public String fetchNativePackageName() {
return packageInfo.packageName;
}

public void getDataFromUri(String uri, Promise promise) {
try {
Uri contentUri = Uri.parse(uri);
try (InputStream is =
getReactApplicationContext().getContentResolver().openInputStream(contentUri)) {
if (is == null) {
String msg = "File not found for uri: " + uri;
logger.log(SentryLevel.ERROR, msg);
promise.reject(new Exception(msg));
return;
}

ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len;
while ((len = is.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
byte[] byteArray = byteBuffer.toByteArray();
WritableArray jsArray = Arguments.createArray();
for (byte b : byteArray) {
jsArray.pushInt(b & 0xFF);
}
promise.resolve(jsArray);
}
} catch (IOException e) {
String msg = "Error reading uri: " + uri + ": " + e.getMessage();
logger.log(SentryLevel.ERROR, msg);
promise.reject(new Exception(msg));
}
}

public void crashedLastRun(Promise promise) {
promise.resolve(Sentry.isCrashedLastRun());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,9 @@ public void crashedLastRun(Promise promise) {
public void getNewScreenTimeToDisplay(Promise promise) {
this.impl.getNewScreenTimeToDisplay(promise);
}

@Override
public void getDataFromUri(String uri, Promise promise) {
this.impl.getDataFromUri(uri, promise);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ public String fetchNativePackageName() {
return this.impl.fetchNativePackageName();
}

@ReactMethod
public void getDataFromUri(String uri, Promise promise) {
this.impl.getDataFromUri(uri, promise);
}

@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) {
// Not used on Android
Expand Down
29 changes: 29 additions & 0 deletions packages/core/ios/RNSentry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,35 @@ - (NSDictionary *)fetchNativeStackFramesBy:(NSArray<NSNumber *> *)instructionsAd
#endif
}

RCT_EXPORT_METHOD(getDataFromUri
: (NSString *_Nonnull)uri resolve
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject)
{
#if TARGET_OS_IPHONE || TARGET_OS_MACCATALYST
NSURL *fileURL = [NSURL URLWithString:uri];
if (![fileURL isFileURL]) {
reject(@"SentryReactNative", @"The provided URI is not a valid file:// URL", nil);
return;
}
NSError *error = nil;
NSData *fileData = [NSData dataWithContentsOfURL:fileURL options:0 error:&error];
if (error || !fileData) {
reject(@"SentryReactNative", @"Failed to read file data", error);
return;
}
NSMutableArray *byteArray = [NSMutableArray arrayWithCapacity:fileData.length];
const unsigned char *bytes = (const unsigned char *)fileData.bytes;

for (NSUInteger i = 0; i < fileData.length; i++) {
[byteArray addObject:@(bytes[i])];
}
resolve(byteArray);
#else
resolve(nil);
#endif
}

RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getCurrentReplayId)
{
#if SENTRY_TARGET_REPLAY_SUPPORTED
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/js/NativeRNSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface Spec extends TurboModule {
captureReplay(isHardCrash: boolean): Promise<string | undefined | null>;
getCurrentReplayId(): string | undefined | null;
crashedLastRun(): Promise<boolean | undefined | null>;
getDataFromUri(uri: string): Promise<number[]>;
}

export type NativeStackFrame = {
Expand Down
111 changes: 111 additions & 0 deletions packages/core/src/js/feedback/FeedbackForm.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { ViewStyle } from 'react-native';

import type { FeedbackFormStyles } from './FeedbackForm.types';

const PURPLE = 'rgba(88, 74, 192, 1)';
const FORGROUND_COLOR = '#2b2233';
const BACKROUND_COLOR = '#ffffff';
const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)';

const defaultStyles: FeedbackFormStyles = {
container: {
flex: 1,
padding: 20,
backgroundColor: BACKROUND_COLOR,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'left',
flex: 1,
color: FORGROUND_COLOR,
},
label: {
marginBottom: 4,
fontSize: 16,
color: FORGROUND_COLOR,
},
input: {
height: 50,
borderColor: BORDER_COLOR,
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 15,
fontSize: 16,
color: FORGROUND_COLOR,
},
textArea: {
height: 100,
textAlignVertical: 'top',
color: FORGROUND_COLOR,
},
screenshotButton: {
backgroundColor: '#eee',
padding: 15,
borderRadius: 5,
marginBottom: 20,
alignItems: 'center',
},
screenshotText: {
color: '#333',
fontSize: 16,
},
submitButton: {
backgroundColor: PURPLE,
paddingVertical: 15,
borderRadius: 5,
alignItems: 'center',
marginBottom: 10,
},
submitText: {
color: BACKROUND_COLOR,
fontSize: 18,
},
cancelButton: {
paddingVertical: 15,
alignItems: 'center',
},
cancelText: {
color: FORGROUND_COLOR,
fontSize: 16,
},
titleContainer: {
flexDirection: 'row',
width: '100%',
},
sentryLogo: {
width: 40,
height: 40,
},
};

export const modalWrapper: ViewStyle = {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
};

export const modalBackground: ViewStyle = {
flex: 1,
justifyContent: 'flex-end',
};

export const modalSheetContainer: ViewStyle = {
backgroundColor: '#ffffff',
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
overflow: 'hidden',
alignSelf: 'stretch',
height: '92%',
shadowColor: '#000',
shadowOffset: { width: 0, height: -3 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 5,
};

export default defaultStyles;
Loading
Loading