projectEventRedirect.tsx 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import {useEffect, useState} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import DetailedError from 'sentry/components/errors/detailedError';
  4. import * as Layout from 'sentry/components/layouts/thirds';
  5. import {t} from 'sentry/locale';
  6. type Props = RouteComponentProps<{}, {}>;
  7. /**
  8. * This component performs a client-side redirect to Event Details given only
  9. * an event ID (which normally additionally requires the event's Issue/Group ID).
  10. * It does this by using an XHR against the identically-named ProjectEventRedirect
  11. * _Django_ view, which responds with a 302 with the Location of the corresponding
  12. * Event Details page (if it exists).
  13. *
  14. * See:
  15. * https://github.com/getsentry/sentry/blob/824c03089907ad22a9282303a5eaca33989ce481/src/sentry/web/urls.py#L578
  16. */
  17. function ProjectEventRedirect({router}: Props) {
  18. const [error, setError] = useState<string | null>(null);
  19. useEffect(() => {
  20. // This presumes that _this_ React view/route is only reachable at
  21. // /:org/:project/events/:eventId (the same URL which serves the ProjectEventRedirect
  22. // Django view).
  23. const endpoint = router.location.pathname;
  24. // Use XmlHttpRequest directly instead of our client API helper (fetch),
  25. // because you can't reach the underlying XHR via $.ajax, and we need
  26. // access to `xhr.responseURL`.
  27. //
  28. // TODO(epurkhiser): We can likely replace tihs with fetch
  29. const xhr = new XMLHttpRequest();
  30. // Hitting this endpoint will return a 302 with a new location, which
  31. // the XHR will follow and make a _second_ request. Using HEAD instead
  32. // of GET returns an empty response instead of the entire HTML content.
  33. xhr.open('HEAD', endpoint);
  34. xhr.send();
  35. xhr.onload = () => {
  36. if (xhr.status === 404) {
  37. setError(t('Could not find an issue for the provided event id'));
  38. return;
  39. }
  40. // responseURL is the URL of the document the browser ultimately loaded,
  41. // after following any redirects. It _should_ be the page we're trying
  42. // to reach; use the router to go there.
  43. // Use `replace` so that hitting the browser back button will skip all
  44. // this redirect business.
  45. router.replace(xhr.responseURL);
  46. };
  47. xhr.onerror = () => {
  48. setError(t('Could not load the requested event'));
  49. };
  50. });
  51. return error ? (
  52. <DetailedError heading={t('Not found')} message={error} hideSupportLinks />
  53. ) : (
  54. <Layout.Page withPadding />
  55. );
  56. }
  57. export default ProjectEventRedirect;