replayDataUtils.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import invariant from 'invariant';
  2. import {duration} from 'moment';
  3. import isValidDate from 'sentry/utils/date/isValidDate';
  4. import getMinMax from 'sentry/utils/getMinMax';
  5. import type {ReplayRecord} from 'sentry/views/replays/types';
  6. export function mapResponseToReplayRecord(apiResponse: any): ReplayRecord {
  7. // Marshal special fields into tags
  8. const user = Object.fromEntries(
  9. Object.entries(apiResponse.user)
  10. .filter(([key, value]) => key !== 'display_name' && value)
  11. .map(([key, value]) => [`user.${key}`, [value]])
  12. );
  13. const unorderedTags: ReplayRecord['tags'] = {
  14. ...apiResponse.tags,
  15. ...(apiResponse.browser?.name ? {'browser.name': [apiResponse.browser.name]} : {}),
  16. ...(apiResponse.browser?.version
  17. ? {'browser.version': [apiResponse.browser.version]}
  18. : {}),
  19. ...(apiResponse.device?.brand ? {'device.brand': [apiResponse.device.brand]} : {}),
  20. ...(apiResponse.device?.family ? {'device.family': [apiResponse.device.family]} : {}),
  21. ...(apiResponse.device?.model_id
  22. ? {'device.model_id': [apiResponse.device.model_id]}
  23. : {}),
  24. ...(apiResponse.device?.name ? {'device.name': [apiResponse.device.name]} : {}),
  25. ...(apiResponse.environment ? {environment: [apiResponse.environment]} : {}),
  26. ...(apiResponse.platform ? {platform: [apiResponse.platform]} : {}),
  27. ...(apiResponse.releases ? {releases: [...apiResponse.releases]} : {}),
  28. ...(apiResponse.replay_type ? {replayType: [apiResponse.replay_type]} : {}),
  29. ...(apiResponse.os?.name ? {'os.name': [apiResponse.os.name]} : {}),
  30. ...(apiResponse.os?.version ? {'os.version': [apiResponse.os.version]} : {}),
  31. ...(apiResponse.sdk?.name ? {'sdk.name': [apiResponse.sdk.name]} : {}),
  32. ...(apiResponse.sdk?.version ? {'sdk.version': [apiResponse.sdk.version]} : {}),
  33. ...user,
  34. };
  35. // Sort the tags by key
  36. const tags = Object.keys(unorderedTags)
  37. .sort()
  38. .reduce((acc, key) => {
  39. acc[key] = unorderedTags[key];
  40. return acc;
  41. }, {});
  42. const startedAt = new Date(apiResponse.started_at);
  43. invariant(isValidDate(startedAt), 'replay.started_at is invalid');
  44. const finishedAt = new Date(apiResponse.finished_at);
  45. invariant(isValidDate(finishedAt), 'replay.finished_at is invalid');
  46. return {
  47. ...apiResponse,
  48. ...(apiResponse.started_at ? {started_at: startedAt} : {}),
  49. ...(apiResponse.finished_at ? {finished_at: finishedAt} : {}),
  50. ...(apiResponse.duration !== undefined
  51. ? {duration: duration(apiResponse.duration * 1000)}
  52. : {}),
  53. tags,
  54. };
  55. }
  56. /**
  57. * We need to figure out the real start and end timestamps based on when
  58. * first and last bits of data were collected. In milliseconds.
  59. *
  60. * @deprecated Once the backend returns the corrected timestamps, this is not needed.
  61. */
  62. export function replayTimestamps(
  63. replayRecord: ReplayRecord,
  64. rrwebEvents: {timestamp: number}[],
  65. rawCrumbs: {timestamp: number}[],
  66. rawSpanData: {endTimestamp: number; op: string; startTimestamp: number}[]
  67. ) {
  68. const rrwebTimestamps = rrwebEvents.map(event => event.timestamp).filter(Boolean);
  69. const breadcrumbTimestamps = rawCrumbs
  70. .map(rawCrumb => rawCrumb.timestamp)
  71. .filter(Boolean);
  72. const rawSpanDataFiltered = rawSpanData.filter(
  73. ({op}) => op !== 'largest-contentful-paint'
  74. );
  75. const spanStartTimestamps = rawSpanDataFiltered
  76. .map(span => span.startTimestamp)
  77. .filter(Boolean);
  78. const spanEndTimestamps = rawSpanDataFiltered
  79. .map(span => span.endTimestamp)
  80. .filter(Boolean);
  81. // Calculate min/max of each array individually, to prevent extra allocations.
  82. // Also using `getMinMax()` so we can handle any huge arrays.
  83. const {min: minRRWeb, max: maxRRWeb} = getMinMax(rrwebTimestamps);
  84. const {min: minCrumbs, max: maxCrumbs} = getMinMax(breadcrumbTimestamps);
  85. const {min: minSpanStarts} = getMinMax(spanStartTimestamps);
  86. const {max: maxSpanEnds} = getMinMax(spanEndTimestamps);
  87. return {
  88. startTimestampMs: Math.min(
  89. replayRecord.started_at.getTime(),
  90. minRRWeb,
  91. minCrumbs * 1000,
  92. minSpanStarts * 1000
  93. ),
  94. endTimestampMs: Math.max(
  95. replayRecord.finished_at.getTime(),
  96. maxRRWeb,
  97. maxCrumbs * 1000,
  98. maxSpanEnds * 1000
  99. ),
  100. };
  101. }