routeError.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import {Component} from 'react';
  2. import {withRouter, WithRouterProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import * as Sentry from '@sentry/react';
  5. import Alert from 'app/components/alert';
  6. import {IconWarning} from 'app/icons';
  7. import {t, tct} from 'app/locale';
  8. import space from 'app/styles/space';
  9. import {LightWeightOrganization, Project} from 'app/types';
  10. import getRouteStringFromRoutes from 'app/utils/getRouteStringFromRoutes';
  11. import withOrganization from 'app/utils/withOrganization';
  12. import withProject from 'app/utils/withProject';
  13. type Props = WithRouterProps & {
  14. organization: LightWeightOrganization;
  15. error: Error | undefined;
  16. /**
  17. * Disable logging to Sentry
  18. */
  19. disableLogSentry?: boolean;
  20. disableReport?: boolean;
  21. project?: Project;
  22. };
  23. class RouteError extends Component<Props> {
  24. UNSAFE_componentWillMount() {
  25. const {error} = this.props;
  26. const {disableLogSentry, disableReport, organization, project, routes} = this.props;
  27. if (disableLogSentry) {
  28. return;
  29. }
  30. if (!error) {
  31. return;
  32. }
  33. const route = getRouteStringFromRoutes(routes);
  34. const enrichScopeContext = scope => {
  35. scope.setExtra('route', route);
  36. scope.setExtra('orgFeatures', (organization && organization.features) || []);
  37. scope.setExtra('orgAccess', (organization && organization.access) || []);
  38. scope.setExtra('projectFeatures', (project && project.features) || []);
  39. return scope;
  40. };
  41. if (route) {
  42. /**
  43. * Unexpectedly, error.message would sometimes not have a setter property, causing another exception to be thrown,
  44. * and losing the original error in the process. Wrapping the mutation in a try-catch in an attempt to preserve
  45. * the original error for logging.
  46. * See https://github.com/getsentry/sentry/issues/16314 for more details.
  47. */
  48. try {
  49. error.message = `${error.message}: ${route}`;
  50. } catch (e) {
  51. Sentry.withScope(scope => {
  52. enrichScopeContext(scope);
  53. Sentry.captureException(e);
  54. });
  55. }
  56. }
  57. // TODO(dcramer): show something in addition to embed (that contains it?)
  58. // throw this in a timeout so if it errors we dont fall over
  59. this._timeout = window.setTimeout(() => {
  60. Sentry.withScope(scope => {
  61. enrichScopeContext(scope);
  62. Sentry.captureException(error);
  63. });
  64. if (!disableReport) {
  65. Sentry.showReportDialog();
  66. }
  67. });
  68. }
  69. componentWillUnmount() {
  70. if (this._timeout) {
  71. window.clearTimeout(this._timeout);
  72. }
  73. document.querySelector('.sentry-error-embed-wrapper')?.remove();
  74. }
  75. private _timeout: undefined | number;
  76. render() {
  77. // TODO(dcramer): show additional resource links
  78. return (
  79. <Alert icon={<IconWarning size="md" />} type="error">
  80. <Heading>
  81. <span>{t('Oops! Something went wrong')}</span>
  82. </Heading>
  83. <p>
  84. {t(`
  85. It looks like you've hit an issue in our client application. Don't worry though!
  86. We use Sentry to monitor Sentry and it's likely we're already looking into this!
  87. `)}
  88. </p>
  89. <p>{t("If you're daring, you may want to try the following:")}</p>
  90. <ul>
  91. {window && window.adblockSuspected && (
  92. <li>
  93. {t(
  94. "We detected something AdBlock-like. Try disabling it, as it's known to cause issues."
  95. )}
  96. </li>
  97. )}
  98. <li>
  99. {tct(`Give it a few seconds and [link:reload the page].`, {
  100. link: (
  101. <a
  102. onClick={() => {
  103. window.location.href = window.location.href;
  104. }}
  105. />
  106. ),
  107. })}
  108. </li>
  109. <li>
  110. {tct(`If all else fails, [link:contact us] with more details.`, {
  111. link: <a href="https://github.com/getsentry/sentry/issues/new/choose" />,
  112. })}
  113. </li>
  114. </ul>
  115. </Alert>
  116. );
  117. }
  118. }
  119. const Heading = styled('h3')`
  120. display: flex;
  121. align-items: center;
  122. font-size: ${p => p.theme.headerFontSize};
  123. font-weight: normal;
  124. margin-bottom: ${space(1.5)};
  125. `;
  126. export default withRouter(withOrganization(withProject(RouteError)));
  127. export {RouteError};