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

(1) feat: Add Feedback From Component #4328

Open
wants to merge 71 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
817eac8
Update the client implementation to use the new capture feedback js api
antonis Nov 27, 2024
5370a99
Updates SDK API
antonis Nov 27, 2024
42e2fa1
Adds new feedback button in the sample
antonis Nov 27, 2024
514b102
Adds changelog
antonis Nov 27, 2024
0935bbd
Removes unused mock
antonis Nov 27, 2024
9ea5496
Update CHANGELOG.md
antonis Nov 28, 2024
da0d4ac
Directly use captureFeedback from sentry/core
antonis Nov 28, 2024
3e36c6d
Use import from core
antonis Nov 28, 2024
5f3df64
Fixes imports order lint issue
antonis Nov 28, 2024
71b28e8
Fixes build issue
antonis Nov 28, 2024
f9d2b59
Adds captureFeedback tests from sentry-javascript
antonis Nov 28, 2024
d05d531
Update CHANGELOG.md
krystofwoldrich Nov 28, 2024
2bb104b
Only deprecate client captureUserFeedback
antonis Nov 28, 2024
4339274
Add simple form UI
antonis Nov 29, 2024
6ce799b
Adds basic form functionality
antonis Nov 29, 2024
f2cefc6
Update imports
antonis Nov 29, 2024
694ee33
Update imports
antonis Dec 2, 2024
18b1c33
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 2, 2024
034efde
Remove useState hook to avoid multiple react instances issues
antonis Dec 2, 2024
67a492d
Move types and styles in different files
antonis Dec 2, 2024
8eaa61d
Removes attachment button to be added back separately along with the …
antonis Dec 2, 2024
0b88cc5
Add basic field validation
antonis Dec 2, 2024
ae11b8d
Adds changelog
antonis Dec 2, 2024
7f2ca06
Updates changelog
antonis Dec 2, 2024
064b6c4
Updates changelog
antonis Dec 2, 2024
e2add4a
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 2, 2024
e21718a
Trim whitespaces from the submitted feedback
antonis Dec 2, 2024
407f179
Adds tests
antonis Dec 2, 2024
14ac005
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 2, 2024
ddade00
Renames FeedbackFormScreen to FeedbackForm
antonis Dec 6, 2024
1bc1e4c
Add beta label
antonis Dec 6, 2024
7934756
Extract default text to constants
antonis Dec 6, 2024
95e1e0f
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 10, 2024
a169362
Moves constant to a separate file and aligns naming with JS
antonis Dec 10, 2024
4b5df7a
Adds input text labels
antonis Dec 10, 2024
4fa81ce
Close screen before sending the feedback to minimise wait time
antonis Dec 10, 2024
4fff82f
Rename file for consistency
antonis Dec 10, 2024
0258bf2
Flatten configuration hierarchy and clean up
antonis Dec 10, 2024
458ebc2
Align required values with JS
antonis Dec 10, 2024
f0e1bef
Use Sentry user email and name when set
antonis Dec 10, 2024
b9235f2
Simplifies email validation
antonis Dec 10, 2024
6717a84
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 11, 2024
39a67bd
Show success alert message
antonis Dec 11, 2024
501a134
Aligns naming with JS and unmounts the form by default
antonis Dec 11, 2024
4b290a2
Use the minimum config without props in the changelog
antonis Dec 11, 2024
7109deb
Adds development not for unimplemented function
antonis Dec 11, 2024
c80c5cb
Show email and name conditionally
antonis Dec 11, 2024
8c56753
Adds sentry branding (png logo)
antonis Dec 11, 2024
d6e9229
Adds sentry logo resource
antonis Dec 11, 2024
5292475
Add assets in module exports
antonis Dec 11, 2024
efd809f
Revert "Add assets in module exports"
antonis Dec 11, 2024
bc7ae65
Revert "Adds sentry logo resource"
antonis Dec 11, 2024
79ee5ba
Revert "Adds sentry branding (png logo)"
antonis Dec 11, 2024
ba13320
Add last event id
antonis Dec 11, 2024
1f5fb56
Mock lastEventId
antonis Dec 11, 2024
439367a
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 13, 2024
9831482
Adds beta note in the changelog
antonis Dec 13, 2024
4097347
Updates changelog
antonis Dec 13, 2024
f8a82fd
Merge branch 'main' into antonis/3859-newCaptureFeedbackAPI-Form
antonis Dec 13, 2024
30a7b10
Align colors with JS
antonis Dec 13, 2024
3eccf25
Update CHANGELOG.md
antonis Dec 13, 2024
bc96fce
Update CHANGELOG.md
antonis Dec 13, 2024
995a9ca
Update CHANGELOG.md
antonis Dec 13, 2024
3aacaf7
Use regular fonts for both buttons
antonis Dec 13, 2024
20e3a6c
Merge branch 'antonis/3859-newCaptureFeedbackAPI-Form' of https://git…
antonis Dec 13, 2024
78e412c
Handle keyboard properly
antonis Dec 13, 2024
c45a5e6
Adds an option on whether the email should be validated
antonis Dec 13, 2024
6e39119
Merge properties only once
antonis Dec 13, 2024
57d99e9
Loads current user data on form construction
antonis Dec 13, 2024
6fb8ab4
Remove unneeded extra padding
antonis Dec 13, 2024
fd2e317
Fix background color issue
antonis Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@
});
```

- Adds feedback form (beta) ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
antonis marked this conversation as resolved.
Show resolved Hide resolved

You can add the form component in your UI like:
antonis marked this conversation as resolved.
Show resolved Hide resolved
```jsx
import { FeedbackForm } from "@sentry/react-native";
...
<FeedbackForm/>
```

- Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))


antonis marked this conversation as resolved.
Show resolved Hide resolved
### Fixes

- Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))
Expand Down
54 changes: 54 additions & 0 deletions packages/core/src/js/feedback/FeedbackForm.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { FeedbackFormStyles } from './FeedbackForm.types';

const defaultStyles: FeedbackFormStyles = {
Copy link
Member

Choose a reason for hiding this comment

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

Let's match the default with JS.

Mainly I've noticed submitButton backgroundColor (JS has rgba(88, 74, 192, 1)) and test color #2b2233

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Tried to align with the light theme of JS with 30a7b10

Screenshot

Simulator Screenshot - iPhone SE (3rd generation) - 2024-12-13 at 19 40 33

container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
label: {
marginBottom: 4,
fontSize: 16,
},
input: {
height: 50,
borderColor: '#ccc',
borderWidth: 1,
borderRadius: 5,
paddingHorizontal: 10,
marginBottom: 15,
fontSize: 16,
},
textArea: {
height: 100,
textAlignVertical: 'top',
},
submitButton: {
backgroundColor: '#6a1b9a',
paddingVertical: 15,
borderRadius: 5,
alignItems: 'center',
marginBottom: 10,
},
submitText: {
color: '#fff',
fontSize: 18,
fontWeight: 'bold',
Copy link
Member

Choose a reason for hiding this comment

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

This might be a personal preference, but I would make both the button bold or both regular.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated with 3aacaf7 to use regular fonts weight.

},
cancelButton: {
paddingVertical: 15,
alignItems: 'center',
},
cancelText: {
color: '#6a1b9a',
fontSize: 16,
},
};

export default defaultStyles;
143 changes: 143 additions & 0 deletions packages/core/src/js/feedback/FeedbackForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { captureFeedback, lastEventId } from '@sentry/core';
import type { SendFeedbackParams } from '@sentry/types';
import * as React from 'react';
import type { KeyboardTypeOptions } from 'react-native';
import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';

import { defaultConfiguration } from './defaults';
import defaultStyles from './FeedbackForm.styles';
import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';

/**
* @beta
* Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
*/
export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
public constructor(props: FeedbackFormProps) {
super(props);

const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props };
this.state = {
isVisible: true,
name: config.useSentryUser.name,
email: config.useSentryUser.email,
description: '',
};
}

public handleFeedbackSubmit: () => void = () => {
const { name, email, description } = this.state;
const { onFormClose } = { ...defaultConfiguration, ...this.props };
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };

const trimmedName = name?.trim();
const trimmedEmail = email?.trim();
const trimmedDescription = description?.trim();

if ((config.isNameRequired && !trimmedName) || (config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
Alert.alert(text.errorTitle, text.formError);
return;
}

if ((config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
Copy link
Member

Choose a reason for hiding this comment

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

Since we are using custom regex for the email validation, let's add an option to enable/disable the validation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added an option with c45a5e6

Alert.alert(text.errorTitle, text.emailError);
return;
}

const eventId = lastEventId();
const userFeedback: SendFeedbackParams = {
message: trimmedDescription,
name: trimmedName,
email: trimmedEmail,
associatedEventId: eventId,
};

onFormClose();
this.setState({ isVisible: false });

captureFeedback(userFeedback);
Alert.alert(text.successMessageText);
};

/**
* Renders the feedback form screen.
*/
public render(): React.ReactNode {
const { name, email, description } = this.state;
const { onFormClose } = { ...defaultConfiguration, ...this.props };
const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
Copy link
Member

Choose a reason for hiding this comment

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

Let do the merge only once { ...defaultConfiguration, ...this.props }.

On moder devices it should not make a perf difference, but let's be safe.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good idea 👍
Updated with 6e39119

const onCancel = (): void => {
onFormClose();
this.setState({ isVisible: false });
}

if (!this.state.isVisible) {
return null;
}

return (
<View style={styles.container}>
<Text style={styles.title}>{text.formTitle}</Text>

{config.showName && (
<>
<Text style={styles.label}>
{text.nameLabel}
{config.isNameRequired && ` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={styles.input}
placeholder={text.namePlaceholder}
value={name}
onChangeText={(value) => this.setState({ name: value })}
/>
</>
)}

{config.showEmail && (
<>
<Text style={styles.label}>
{text.emailLabel}
{config.isEmailRequired && ` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={styles.input}
placeholder={text.emailPlaceholder}
keyboardType={'email-address' as KeyboardTypeOptions}
value={email}
onChangeText={(value) => this.setState({ email: value })}
/>
</>
)}

<Text style={styles.label}>
{text.messageLabel}
{` ${text.isRequiredLabel}`}
</Text>
<TextInput
style={[styles.input, styles.textArea]}
placeholder={text.messagePlaceholder}
value={description}
onChangeText={(value) => this.setState({ description: value })}
multiline
/>

<TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
<Text style={styles.submitText}>{text.submitButtonLabel}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
<Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
</TouchableOpacity>
</View>
);
}

private _isValidEmail = (email: string): boolean => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return emailRegex.test(email);
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
};
}
143 changes: 143 additions & 0 deletions packages/core/src/js/feedback/FeedbackForm.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type { TextStyle, ViewStyle } from 'react-native';

export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

styles?: FeedbackFormStyles;
}

/**
* General feedback configuration
*/
export interface FeedbackGeneralConfiguration {
/**
* Should the email field be required?
*/
isEmailRequired?: boolean;

/**
* Should the name field be required?
*/
isNameRequired?: boolean;

/**
* Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()`
*/
showEmail?: boolean;

/**
* Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
*/
showName?: boolean;

/**
* Fill in email/name input fields with Sentry user context if it exists.
* The value of the email/name keys represent the properties of your user context.
*/
useSentryUser?: {
email: string;
name: string;
};
}

/**
* All of the different text labels that can be customized
*/
export interface FeedbackTextConfiguration {
/**
* The label for the Feedback form cancel button that closes dialog
*/
cancelButtonLabel?: string;

/**
* The label for the Feedback form submit button that sends feedback
*/
submitButtonLabel?: string;

/**
* The title of the Feedback form
*/
formTitle?: string;

/**
* Label for the email input
*/
emailLabel?: string;

/**
* Placeholder text for Feedback email input
*/
emailPlaceholder?: string;

/**
* Label for the message input
*/
messageLabel?: string;

/**
* Placeholder text for Feedback message input
*/
messagePlaceholder?: string;

/**
* Label for the name input
*/
nameLabel?: string;

/**
* Message after feedback was sent successfully
*/
successMessageText?: string;

/**
* Placeholder text for Feedback name input
*/
namePlaceholder?: string;

/**
* Text which indicates that a field is required
*/
isRequiredLabel?: string;

/**
* The title of the error dialog
*/
errorTitle?: string;

/**
* The error message when the form is invalid
*/
formError?: string;

/**
* The error message when the email is invalid
*/
emailError?: string;
}

/**
* The public callbacks available for the feedback integration
*/
export interface FeedbackCallbacks {
/**
* Callback when form is closed and not submitted
*/
onFormClose?: () => void;
krystofwoldrich marked this conversation as resolved.
Show resolved Hide resolved
}

export interface FeedbackFormStyles {
container?: ViewStyle;
title?: TextStyle;
label?: TextStyle;
input?: TextStyle;
textArea?: TextStyle;
submitButton?: ViewStyle;
submitText?: TextStyle;
cancelButton?: ViewStyle;
cancelText?: TextStyle;
}

export interface FeedbackFormState {
isVisible: boolean;
name: string;
email: string;
description: string;
}
Loading
Loading