details.tsx 5.3 KB

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