routeError.tsx 4.5 KB

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