Skip to content

Commit

Permalink
feat(1852): add eventId to sentry feedback report
Browse files Browse the repository at this point in the history
  • Loading branch information
DDDDDanica committed Oct 19, 2024
1 parent 1c8f366 commit 35ca109
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 66 deletions.
24 changes: 0 additions & 24 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

169 changes: 130 additions & 39 deletions ui/pages/error-page/error-page.component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useContext, useRef } from 'react';
import React, { useEffect, useContext, useRef, useState } from 'react';
import * as Sentry from '@sentry/browser';
import browser from 'webextension-polyfill';
import {
Expand All @@ -7,6 +7,7 @@ import {
MetaMetricsEventName,
} from '../../../shared/constants/metametrics';

import { getParticipateInMetaMetrics } from '../../selectors';
import { MetaMetricsContext } from '../../contexts/metametrics';
import { useI18nContext } from '../../hooks/useI18nContext';
import {
Expand All @@ -18,6 +19,12 @@ import {
Text,
Button,
ButtonVariant,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
} from '../../components/component-library';
import {
AlignItems,
Expand All @@ -28,10 +35,16 @@ import {
FlexDirection,
IconColor,
JustifyContent,
TextColor,
TextVariant,
} from '../../helpers/constants/design-system';

import { SUPPORT_REQUEST_LINK } from '../../helpers/constants/common';
import { useSelector } from 'react-redux';
import { Textarea } from '../../components/component-library/textarea/textarea';
import { TextareaResize } from '../../components/component-library/textarea/textarea.types';
import { ButtonSize } from '../../components/component-library/button/button.types';
import { BannerAlertSeverity } from '../../components/component-library/banner-alert/banner-alert.types';

type ErrorPageProps = {
error: {
Expand All @@ -45,40 +58,42 @@ type ErrorPageProps = {
const ErrorPage: React.FC<ErrorPageProps> = ({ error }) => {
const t = useI18nContext();
const trackEvent = useContext(MetaMetricsContext);
const sentryButtonRef = useRef(null);
const isMetaMetricsEnabled = useSelector(getParticipateInMetaMetrics);

useEffect(() => {
// Initialize the Sentry feedback integration widget
const feedback = Sentry.feedbackIntegration({
enableScreenshot: false,
autoInject: false,
showBranding: false,
showName: false,
showEmail: false,
triggerLabel: t('errorPageSentryTriggerLabel'),
formTitle: t('errorPageSentryFormTitle'),
submitButtonLabel: t('errorPageSentrySubmitButtonLabel'),
cancelButtonLabel: t('errorPageSentryCancelButtonLabel'),
confirmButtonLabel: t('errorPageSentryConfirmButtonLabel'),
isRequiredLabel: t('errorPageSentryIsRequiredLabel'),
messageLabel: t('errorPageSentryMessageLabel'),
messagePlaceholder: t('errorPageSentryMessagePlaceholder'),
errorPageSuccessMessageText: t('errorPageSentrySuccessMessageText'),
const [feedbackMessage, setFeedbackMessage] = useState('');
const [isFeedbackModalOpen, setIsFeedbackModalOpen] = useState(false);
const [isSuccessModalShown, setIsSuccessModalShown] = useState(false);

const handleClickDescribeButton = (): void => {
setIsFeedbackModalOpen(true);
};

const handleCloseDescribeModal = (): void => {
setIsFeedbackModalOpen(false);
};

const handleSubmitFeedback = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const eventId = Sentry.lastEventId();

Sentry.captureFeedback({
message: feedbackMessage,
associatedEventId: eventId,
});
handleCloseDescribeModal();
setIsSuccessModalShown(true);
};

let isMounted = true; // For preventing memory leak
useEffect(() => {
if (isSuccessModalShown) {
const timeoutId = setTimeout(() => {
setIsSuccessModalShown(false); // Close the modal after 5 seconds
}, 5000);

if (sentryButtonRef.current && isMounted) {
feedback.attachTo(sentryButtonRef.current); // Attach feedback widget to button
// Cleanup function to clear timeout if the component unmounts or state changes
return () => clearTimeout(timeoutId);
}

return () => {
isMounted = false; // Prevents async operations on unmounted component
if (feedback) {
feedback.remove(); // Clean up feedback widget
}
};
}, [sentryButtonRef, t]);
}, [isSuccessModalShown]);

return (
<section className="error-page">
Expand Down Expand Up @@ -157,6 +172,80 @@ const ErrorPage: React.FC<ErrorPageProps> = ({ error }) => {
) : null}
</Box>

{isFeedbackModalOpen && (
<Modal
isOpen={isFeedbackModalOpen}
onClose={handleCloseDescribeModal}
>
<ModalOverlay />
<ModalContent>
<ModalHeader onClose={handleCloseDescribeModal}>
{t('errorPageSentryFormTitle')}
</ModalHeader>
<ModalBody>
<Textarea
resize={TextareaResize.Vertical}
required
autoFocus
cols="32"
rows="6"
placeholder={t('errorPageSentryMessagePlaceholder')}
onChange={(e) => setFeedbackMessage(e.target.value)}
/>
</ModalBody>
<ModalFooter>
<Box display={Display.Flex} gap={4}>
<Button
variant={ButtonVariant.Secondary}
width={BlockSize.Half}
onClick={handleCloseDescribeModal}
size={ButtonSize.Md}
>
{t('cancel')}
</Button>
<Button
variant={ButtonVariant.Primary}
width={BlockSize.Half}
onClick={handleSubmitFeedback}
size={ButtonSize.Md}
>
{t('submit')}
</Button>
</Box>
</ModalFooter>
</ModalContent>
</Modal>
)}
{isSuccessModalShown && (
<Modal
isOpen={isSuccessModalShown}
onClose={() => setIsSuccessModalShown(false)}
>
<ModalOverlay />
<ModalContent>
<ModalBody
display={Display.Flex}
flexDirection={FlexDirection.Row}
alignItems={AlignItems.center}
justifyContent={JustifyContent.center}
Gap={4}
>
<Icon
name={IconName.CheckBold}
color={IconColor.successDefault}
size={IconSize.Md}
marginRight={2}
/>
<Text
variant={TextVariant.bodyMdMedium}
color={TextColor.successDefault}
>
{t('errorPageSentrySuccessMessageText')}
</Text>
</ModalBody>
</ModalContent>
</Modal>
)}
<Box
width={BlockSize.Full}
display={Display.Flex}
Expand All @@ -165,15 +254,17 @@ const ErrorPage: React.FC<ErrorPageProps> = ({ error }) => {
justifyContent={JustifyContent.center}
marginTop={4}
>
<Button
ref={sentryButtonRef}
className="error-page__report-to-sentry-button"
marginBottom={2}
block
data-testid="error-page-describe-what-happened-button"
>
{t('errorPageDescribeUsWhatHappened')}
</Button>
{isMetaMetricsEnabled && (
<Button
className="error-page__report-to-sentry-button"
marginBottom={2}
block
data-testid="error-page-describe-what-happened-button"
onClick={handleClickDescribeButton}
>
{t('errorPageDescribeUsWhatHappened')}
</Button>
)}
<Button
marginBottom={2}
variant={ButtonVariant.Secondary}
Expand Down
9 changes: 6 additions & 3 deletions ui/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ import ErrorPage from './error-page/error-page.component';
import Routes from './routes';

class Index extends PureComponent {
state = {};
state = {
sentryEventId: null,
};

static getDerivedStateFromError(error) {
console.log(error);
return { error };
}

Expand All @@ -26,15 +29,15 @@ class Index extends PureComponent {
}

render() {
const { error, errorId } = this.state;
const { error } = this.state;
const { store } = this.props;

if (error) {
return (
<Provider store={store}>
<I18nProvider>
<LegacyI18nProvider>
<ErrorPage error={error} errorId={errorId} />
<ErrorPage error={error} />
</LegacyI18nProvider>
</I18nProvider>
</Provider>
Expand Down

0 comments on commit 35ca109

Please sign in to comment.