rrwebIntegration.tsx 2.9 KB

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