replayDataUtils.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import first from 'lodash/first';
  2. import type {eventWithTime} from 'rrweb/typings/types';
  3. import {getVirtualCrumb} from 'sentry/components/events/interfaces/breadcrumbs/utils';
  4. import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  5. import {t} from 'sentry/locale';
  6. import type {BreadcrumbTypeDefault, RawCrumb} from 'sentry/types/breadcrumbs';
  7. import {BreadcrumbLevelType, BreadcrumbType} from 'sentry/types/breadcrumbs';
  8. import {Entry, EntryType, Event, EventTag} from 'sentry/types/event';
  9. export function rrwebEventListFactory(
  10. startTimestampMS: number,
  11. endTimestampMS: number,
  12. rawSpanData: RawSpanType[],
  13. rrwebEvents: eventWithTime[]
  14. ) {
  15. const highlights = rawSpanData
  16. .filter(({op, data}) => op === 'largest-contentful-paint' && data?.nodeId > 0)
  17. .map(({start_timestamp, data: {nodeId}}) => ({
  18. type: 6, // plugin type
  19. data: {
  20. nodeId,
  21. text: 'LCP',
  22. },
  23. timestamp: Math.floor(start_timestamp * 1000),
  24. }));
  25. const events = ([] as eventWithTime[])
  26. .concat(rrwebEvents)
  27. .concat(highlights)
  28. .concat({
  29. type: 5, // EventType.Custom,
  30. timestamp: endTimestampMS,
  31. data: {
  32. tag: 'replay-end',
  33. },
  34. });
  35. events.sort((a, b) => a.timestamp - b.timestamp);
  36. const firstRRWebEvent = first(events);
  37. if (firstRRWebEvent) {
  38. firstRRWebEvent.timestamp = startTimestampMS;
  39. }
  40. return events;
  41. }
  42. function entriesFromEvents(events: Event[]) {
  43. return events.flatMap(event => event.entries);
  44. }
  45. export function breadcrumbValuesFromEvents(events: Event[]) {
  46. const fromEntries = entriesFromEvents(events).flatMap(entry =>
  47. entry.type === EntryType.BREADCRUMBS ? entry.data.values : []
  48. );
  49. const fromEvents = events.map(getVirtualCrumb).filter(Boolean) as RawCrumb[];
  50. return ([] as RawCrumb[]).concat(fromEntries).concat(fromEvents);
  51. }
  52. export function breadcrumbEntryFactory(
  53. startTimestamp: number,
  54. tags: EventTag[],
  55. rawCrumbs: RawCrumb[]
  56. ) {
  57. const initBreadcrumb = {
  58. type: BreadcrumbType.INIT,
  59. timestamp: new Date(startTimestamp).toISOString(),
  60. level: BreadcrumbLevelType.INFO,
  61. action: 'replay-init',
  62. message: t('Start recording'),
  63. data: {
  64. url: tags.find(tag => tag.key === 'url')?.value,
  65. },
  66. } as BreadcrumbTypeDefault;
  67. const stringified = rawCrumbs.map(value => JSON.stringify(value));
  68. const deduped = Array.from(new Set(stringified));
  69. const values = [initBreadcrumb].concat(deduped.map(value => JSON.parse(value)));
  70. values.sort((a, b) => +new Date(a?.timestamp || 0) - +new Date(b?.timestamp || 0));
  71. return {
  72. type: EntryType.BREADCRUMBS,
  73. data: {
  74. values,
  75. },
  76. } as Entry;
  77. }
  78. export function spanDataFromEvents(events: Event[]) {
  79. return entriesFromEvents(events).flatMap((entry: Entry) =>
  80. entry.type === EntryType.SPANS ? (entry.data as RawSpanType[]) : []
  81. );
  82. }
  83. export function spanEntryFactory(spans: RawSpanType[]) {
  84. spans.sort((a, b) => a.start_timestamp - b.start_timestamp);
  85. return {
  86. type: EntryType.SPANS,
  87. data: spans,
  88. } as Entry;
  89. }
  90. /**
  91. * The original `this._event.startTimestamp` and `this._event.endTimestamp`
  92. * are the same. It's because the root replay event is re-purposing the
  93. * `transaction` type, but it is not a real span occuring over time.
  94. * So we need to figure out the real start and end timestamps based on when
  95. * first and last bits of data were collected. In milliseconds.
  96. */
  97. export function replayTimestamps(
  98. rrwebEvents: eventWithTime[],
  99. rawCrumbs: RawCrumb[],
  100. rawSpanData: RawSpanType[]
  101. ) {
  102. const rrwebTimestamps = rrwebEvents.map(event => event.timestamp);
  103. const breadcrumbTimestamps = (
  104. rawCrumbs.map(rawCrumb => rawCrumb.timestamp).filter(Boolean) as string[]
  105. ).map(timestamp => +new Date(timestamp));
  106. const spanStartTimestamps = rawSpanData.map(span => span.start_timestamp * 1000);
  107. const spanEndTimestamps = rawSpanData.map(span => span.timestamp * 1000);
  108. return {
  109. startTimestampMS: Math.min(
  110. ...[...rrwebTimestamps, ...breadcrumbTimestamps, ...spanStartTimestamps]
  111. ),
  112. endTimestampMS: Math.max(
  113. ...[...rrwebTimestamps, ...breadcrumbTimestamps, ...spanEndTimestamps]
  114. ),
  115. };
  116. }