replayPreview.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import type {ComponentProps} from 'react';
  2. import {useMemo} from 'react';
  3. import styled from '@emotion/styled';
  4. import {Alert} from 'sentry/components/alert';
  5. import type {LinkButton} from 'sentry/components/button';
  6. import {Flex} from 'sentry/components/container/flex';
  7. import NegativeSpaceContainer from 'sentry/components/container/negativeSpaceContainer';
  8. import {REPLAY_LOADING_HEIGHT} from 'sentry/components/events/eventReplay/constants';
  9. import {StaticReplayPreview} from 'sentry/components/events/eventReplay/staticReplayPreview';
  10. import LoadingIndicator from 'sentry/components/loadingIndicator';
  11. import MissingReplayAlert from 'sentry/components/replays/alerts/missingReplayAlert';
  12. import {IconDelete} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import {space} from 'sentry/styles/space';
  15. import type {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab';
  16. import useReplayReader from 'sentry/utils/replays/hooks/useReplayReader';
  17. import type RequestError from 'sentry/utils/requestError/requestError';
  18. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  19. import type {ReplayRecord} from 'sentry/views/replays/types';
  20. type Props = {
  21. analyticsContext: string;
  22. eventTimestampMs: number;
  23. orgSlug: string;
  24. replaySlug: string;
  25. focusTab?: TabKey;
  26. fullReplayButtonProps?: Partial<ComponentProps<typeof LinkButton>>;
  27. };
  28. function getReplayAnalyticsStatus({
  29. fetchError,
  30. replayRecord,
  31. }: {
  32. fetchError?: RequestError;
  33. replayRecord?: ReplayRecord;
  34. }) {
  35. if (fetchError) {
  36. return 'error';
  37. }
  38. if (replayRecord?.is_archived) {
  39. return 'archived';
  40. }
  41. if (replayRecord) {
  42. return 'success';
  43. }
  44. return 'none';
  45. }
  46. function ReplayPreview({
  47. analyticsContext,
  48. fullReplayButtonProps,
  49. eventTimestampMs,
  50. focusTab,
  51. orgSlug,
  52. replaySlug,
  53. }: Props) {
  54. const {fetching, replay, replayRecord, fetchError, replayId} = useReplayReader({
  55. orgSlug,
  56. replaySlug,
  57. });
  58. const startTimestampMs = replayRecord?.started_at?.getTime() ?? 0;
  59. const initialTimeOffsetMs = useMemo(() => {
  60. if (eventTimestampMs && startTimestampMs) {
  61. return Math.abs(eventTimestampMs - startTimestampMs);
  62. }
  63. return 0;
  64. }, [eventTimestampMs, startTimestampMs]);
  65. useRouteAnalyticsParams({
  66. event_replay_status: getReplayAnalyticsStatus({fetchError, replayRecord}),
  67. });
  68. if (replayRecord?.is_archived) {
  69. return (
  70. <Alert type="warning" data-test-id="replay-error">
  71. <Flex gap={space(0.5)}>
  72. <IconDelete color="gray500" size="sm" />
  73. {t('The replay for this event has been deleted.')}
  74. </Flex>
  75. </Alert>
  76. );
  77. }
  78. if (fetchError) {
  79. return <MissingReplayAlert orgSlug={orgSlug} />;
  80. }
  81. if (fetching || !replayRecord || !replay) {
  82. return (
  83. <StyledNegativeSpaceContainer testId="replay-loading-placeholder">
  84. <LoadingIndicator />
  85. </StyledNegativeSpaceContainer>
  86. );
  87. }
  88. return (
  89. <StaticReplayPreview
  90. focusTab={focusTab}
  91. isFetching={fetching}
  92. analyticsContext={analyticsContext}
  93. replay={replay}
  94. replayId={replayId}
  95. fullReplayButtonProps={fullReplayButtonProps}
  96. initialTimeOffsetMs={initialTimeOffsetMs}
  97. />
  98. );
  99. }
  100. const StyledNegativeSpaceContainer = styled(NegativeSpaceContainer)`
  101. height: ${REPLAY_LOADING_HEIGHT}px;
  102. margin-bottom: ${space(2)};
  103. `;
  104. export default ReplayPreview;