rrwebIntegration.tsx 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import {lazy} from 'react';
  2. import styled from '@emotion/styled';
  3. import {EventDataSection} from 'sentry/components/events/eventDataSection';
  4. import LazyLoad from 'sentry/components/lazyLoad';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import type {Event} from 'sentry/types/event';
  9. import type {IssueAttachment} from 'sentry/types/group';
  10. import type {Organization} from 'sentry/types/organization';
  11. import type {Project} from 'sentry/types/project';
  12. import {useApiQuery} from 'sentry/utils/queryClient';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. type Props = {
  15. event: Event;
  16. orgId: Organization['id'];
  17. projectSlug: Project['slug'];
  18. };
  19. const LazyReplayer = lazy(() => import('./rrwebReplayer'));
  20. function EventRRWebIntegrationContent({orgId, projectSlug, event}: Props) {
  21. const {
  22. data: attachmentList,
  23. isLoading,
  24. isError,
  25. refetch,
  26. } = useApiQuery<IssueAttachment[]>(
  27. [
  28. `/projects/${orgId}/${projectSlug}/events/${event.id}/attachments/`,
  29. // This was changed from `rrweb.json`, so that we can instead
  30. // support incremental rrweb events as attachments. This is to avoid
  31. // having clients uploading a single, large sized replay.
  32. //
  33. // Note: This will include all attachments that contain `rrweb`
  34. // anywhere its name. We need to maintain compatibility with existing
  35. // rrweb plugin users (single replay), but also support incremental
  36. // replays as well. I can't think of a reason why someone would have
  37. // a non-rrweb replay containing the string `rrweb`, but people have
  38. // surprised me before.
  39. {query: {query: 'rrweb'}},
  40. ],
  41. {staleTime: 0}
  42. );
  43. if (isError) {
  44. return <LoadingError onRetry={refetch} />;
  45. }
  46. if (isLoading) {
  47. // hide loading indicator
  48. return null;
  49. }
  50. if (!attachmentList?.length) {
  51. return null;
  52. }
  53. const createAttachmentUrl = (attachment: IssueAttachment) =>
  54. `/api/0/projects/${orgId}/${projectSlug}/events/${event.id}/attachments/${attachment.id}/?download`;
  55. return (
  56. <StyledReplayEventDataSection type="context-replay" title={t('Replay')}>
  57. <LazyLoad
  58. LazyComponent={LazyReplayer}
  59. urls={attachmentList.map(createAttachmentUrl)}
  60. />
  61. </StyledReplayEventDataSection>
  62. );
  63. }
  64. export function EventRRWebIntegration(props: Props) {
  65. const organization = useOrganization();
  66. const hasReplay = Boolean(
  67. props.event?.tags?.find(({key}) => key === 'replayId')?.value
  68. );
  69. const hasEventAttachmentsFeature = organization.features.includes('event-attachments');
  70. if (hasReplay || !hasEventAttachmentsFeature) {
  71. return null;
  72. }
  73. return <EventRRWebIntegrationContent {...props} />;
  74. }
  75. const StyledReplayEventDataSection = styled(EventDataSection)`
  76. overflow: hidden;
  77. margin-bottom: ${space(3)};
  78. `;