details.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import {Fragment} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import DetailedError from 'sentry/components/errors/detailedError';
  4. import NotFound from 'sentry/components/errors/notFound';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import List from 'sentry/components/list';
  7. import ListItem from 'sentry/components/list/listItem';
  8. import {
  9. Provider as ReplayContextProvider,
  10. useReplayContext,
  11. } from 'sentry/components/replays/replayContext';
  12. import {t} from 'sentry/locale';
  13. import useInitialTimeOffsetMs, {
  14. TimeOffsetLocationQueryParams,
  15. } from 'sentry/utils/replays/hooks/useInitialTimeOffsetMs';
  16. import useReplayData from 'sentry/utils/replays/hooks/useReplayData';
  17. import useReplayLayout from 'sentry/utils/replays/hooks/useReplayLayout';
  18. import useReplayPageview from 'sentry/utils/replays/hooks/useReplayPageview';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import ReplaysLayout from 'sentry/views/replays/detail/layout';
  21. import Page from 'sentry/views/replays/detail/page';
  22. import ReplayTransactionContext from 'sentry/views/replays/detail/trace/replayTransactionContext';
  23. import type {ReplayError, ReplayRecord} from 'sentry/views/replays/types';
  24. type Props = RouteComponentProps<
  25. {replaySlug: string},
  26. {},
  27. any,
  28. TimeOffsetLocationQueryParams
  29. >;
  30. function ReplayDetails({params: {replaySlug}}: Props) {
  31. useReplayPageview('replay.details-time-spent');
  32. const organization = useOrganization();
  33. const {slug: orgSlug} = organization;
  34. // TODO: replayId is known ahead of time and useReplayData is parsing it from the replaySlug
  35. // once we fix the route params and links we should fix this to accept replayId and stop returning it
  36. const {
  37. fetching,
  38. onRetry,
  39. replay,
  40. replayRecord,
  41. fetchError,
  42. projectSlug,
  43. replayId,
  44. replayErrors,
  45. } = useReplayData({
  46. replaySlug,
  47. orgSlug,
  48. });
  49. const initialTimeOffsetMs = useInitialTimeOffsetMs({
  50. orgSlug,
  51. projectSlug,
  52. replayId,
  53. replayStartTimestampMs: replayRecord?.started_at.getTime(),
  54. });
  55. if (fetchError) {
  56. if (fetchError.statusText === 'Not Found') {
  57. return (
  58. <Page
  59. orgSlug={orgSlug}
  60. replayRecord={replayRecord}
  61. projectSlug={projectSlug}
  62. replayErrors={replayErrors}
  63. >
  64. <Layout.Page withPadding>
  65. <NotFound />
  66. </Layout.Page>
  67. </Page>
  68. );
  69. }
  70. const reasons = [
  71. t('The replay is still processing'),
  72. t('The replay has been deleted by a member in your organization'),
  73. t('There is an internal systems error'),
  74. ];
  75. return (
  76. <Page
  77. orgSlug={orgSlug}
  78. replayRecord={replayRecord}
  79. projectSlug={projectSlug}
  80. replayErrors={replayErrors}
  81. >
  82. <Layout.Page>
  83. <DetailedError
  84. onRetry={onRetry}
  85. hideSupportLinks
  86. heading={t('There was an error while fetching this Replay')}
  87. message={
  88. <Fragment>
  89. <p>{t('This could be due to these reasons:')}</p>
  90. <List symbol="bullet">
  91. {reasons.map((reason, i) => (
  92. <ListItem key={i}>{reason}</ListItem>
  93. ))}
  94. </List>
  95. </Fragment>
  96. }
  97. />
  98. </Layout.Page>
  99. </Page>
  100. );
  101. }
  102. if (!fetching && replay && replay.getRRWebEvents().length < 2) {
  103. return (
  104. <Page
  105. orgSlug={orgSlug}
  106. replayRecord={replayRecord}
  107. projectSlug={projectSlug}
  108. replayErrors={replayErrors}
  109. >
  110. <DetailedError
  111. hideSupportLinks
  112. heading={t('Error loading replay')}
  113. message={
  114. <Fragment>
  115. <p>
  116. {t(
  117. 'Expected two or more replay events. This Replay may not have captured any user actions.'
  118. )}
  119. </p>
  120. <p>
  121. {t(
  122. 'Or there may be an issue loading the actions from the server, click to try loading the Replay again.'
  123. )}
  124. </p>
  125. </Fragment>
  126. }
  127. />
  128. </Page>
  129. );
  130. }
  131. return (
  132. <ReplayContextProvider
  133. isFetching={fetching}
  134. replay={replay}
  135. initialTimeOffsetMs={initialTimeOffsetMs}
  136. >
  137. <ReplayTransactionContext replayRecord={replayRecord}>
  138. <DetailsInsideContext
  139. orgSlug={orgSlug}
  140. replayRecord={replayRecord}
  141. projectSlug={projectSlug}
  142. replayErrors={replayErrors}
  143. />
  144. </ReplayTransactionContext>
  145. </ReplayContextProvider>
  146. );
  147. }
  148. function DetailsInsideContext({
  149. orgSlug,
  150. replayRecord,
  151. projectSlug,
  152. replayErrors,
  153. }: {
  154. orgSlug: string;
  155. projectSlug: string | null;
  156. replayErrors: ReplayError[];
  157. replayRecord: ReplayRecord | undefined;
  158. }) {
  159. const {getLayout} = useReplayLayout();
  160. const {replay} = useReplayContext();
  161. return (
  162. <Page
  163. orgSlug={orgSlug}
  164. crumbs={replay?.getNavCrumbs()}
  165. replayRecord={replayRecord}
  166. projectSlug={projectSlug}
  167. replayErrors={replayErrors}
  168. >
  169. <ReplaysLayout layout={getLayout()} />
  170. </Page>
  171. );
  172. }
  173. export default ReplayDetails;