replayPreview.tsx 3.1 KB

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