utils.spec.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import {
  2. countColumns,
  3. divide,
  4. flattenFrames,
  5. formatTime,
  6. getFramesByColumn,
  7. relativeTimeInMs,
  8. showPlayerTime,
  9. } from 'sentry/components/replays/utils';
  10. // import {BreadcrumbLevelType, BreadcrumbType, Crumb} from 'sentry/types/breadcrumbs';
  11. import hydrateErrors from 'sentry/utils/replays/hydrateErrors';
  12. import hydrateSpans from 'sentry/utils/replays/hydrateSpans';
  13. const SECOND = 1000;
  14. describe('formatTime', () => {
  15. it.each([
  16. ['seconds', 15 * 1000, '00:15'],
  17. ['minutes', 2.5 * 60 * 1000, '02:30'],
  18. ['hours', 75 * 60 * 1000, '01:15:00'],
  19. ])('should format a %s long duration into a string', (_desc, duration, expected) => {
  20. expect(formatTime(duration)).toEqual(expected);
  21. });
  22. });
  23. describe('countColumns', () => {
  24. it('should divide 27s by 2700px to find twentyseven 1s columns, with some fraction remaining', () => {
  25. // 2700 allows for up to 27 columns at 100px wide.
  26. // That is what we'd need if we were to render at `1s` granularity, so we can.
  27. const width = 2700;
  28. const duration = 27 * SECOND;
  29. const minWidth = 100;
  30. const {timespan, cols, remaining} = countColumns(duration, width, minWidth);
  31. expect(timespan).toBe(1 * SECOND);
  32. expect(cols).toBe(27);
  33. expect(remaining).toBe(0);
  34. });
  35. it('should divide 27s by 2699px to find five 5s columns, with some fraction remaining', () => {
  36. // 2699px allows for up to 26 columns at 100px wide, with 99px leftover.
  37. // That is less than the 27 cols we'd need if we were to render at `1s` granularity.
  38. // So instead we get 5 cols (wider than 100px) at 5s granularity, and some extra space is remaining.
  39. const width = 2699;
  40. const duration = 27 * SECOND;
  41. const minWidth = 100;
  42. const {timespan, cols, remaining} = countColumns(duration, width, minWidth);
  43. expect(timespan).toBe(5 * SECOND);
  44. expect(cols).toBe(5);
  45. expect(remaining).toBe(0.4);
  46. });
  47. it('should divide 27s by 600px to find five 5s columns, with some fraction column remaining', () => {
  48. // 600px allows for 6 columns at 100px wide to fix within it
  49. // That allows us to get 5 cols (100px wide) at 5s granularity, and an extra 100px for the remainder
  50. const width = 600;
  51. const duration = 27 * SECOND;
  52. const minWidth = 100;
  53. const {timespan, cols, remaining} = countColumns(duration, width, minWidth);
  54. expect(timespan).toBe(5 * SECOND);
  55. expect(cols).toBe(5);
  56. expect(remaining).toBe(0.4);
  57. });
  58. it('should divide 27s by 599px to find five 2s columns, with some fraction column remaining', () => {
  59. // 599px allows for 5 columns at 100px wide, and 99px remaining.
  60. // That allows us to get 2 cols (100px wide) at 10s granularity, and an extra px for the remainder
  61. const width = 599;
  62. const duration = 27 * SECOND;
  63. const minWidth = 100;
  64. const {timespan, cols, remaining} = countColumns(duration, width, minWidth);
  65. expect(timespan).toBe(10 * SECOND);
  66. expect(cols).toBe(2);
  67. expect(remaining).toBe(0.7);
  68. });
  69. });
  70. describe('getFramesByColumn', () => {
  71. const durationMs = 25710; // milliseconds
  72. const [CRUMB_1, CRUMB_2, CRUMB_3, CRUMB_4, CRUMB_5] = hydrateErrors(
  73. TestStubs.ReplayRecord({
  74. started_at: new Date('2022-04-14T14:19:47.326000Z'),
  75. }),
  76. [
  77. TestStubs.Replay.RawReplayError({
  78. timestamp: new Date('2022-04-14T14:19:47.326000Z'),
  79. }),
  80. TestStubs.Replay.RawReplayError({
  81. timestamp: new Date('2022-04-14T14:19:49.249000Z'),
  82. }),
  83. TestStubs.Replay.RawReplayError({
  84. timestamp: new Date('2022-04-14T14:19:51.512000Z'),
  85. }),
  86. TestStubs.Replay.RawReplayError({
  87. timestamp: new Date('2022-04-14T14:19:57.326000Z'),
  88. }),
  89. TestStubs.Replay.RawReplayError({
  90. timestamp: new Date('2022-04-14T14:20:13.036000Z'),
  91. }),
  92. ]
  93. );
  94. it('should return an empty list when no crumbs exist', () => {
  95. const columnCount = 3;
  96. const columns = getFramesByColumn(durationMs, [], columnCount);
  97. const expectedEntries = [];
  98. expect(columns).toEqual(new Map(expectedEntries));
  99. });
  100. it('should put a crumbs in the first and last buckets', () => {
  101. const columnCount = 3;
  102. const columns = getFramesByColumn(durationMs, [CRUMB_1, CRUMB_5], columnCount);
  103. expect(columns).toEqual(
  104. new Map([
  105. [1, [CRUMB_1]],
  106. [3, [CRUMB_5]],
  107. ])
  108. );
  109. });
  110. it('should group crumbs by bucket', () => {
  111. // 6 columns gives is 5s granularity
  112. const columnCount = 6;
  113. const columns = getFramesByColumn(
  114. durationMs,
  115. [CRUMB_1, CRUMB_2, CRUMB_3, CRUMB_4, CRUMB_5],
  116. columnCount
  117. );
  118. expect(columns).toEqual(
  119. new Map([
  120. [1, [CRUMB_1, CRUMB_2, CRUMB_3]],
  121. [2, [CRUMB_4]],
  122. [6, [CRUMB_5]],
  123. ])
  124. );
  125. });
  126. });
  127. describe('flattenFrames', () => {
  128. it('should return an empty array if there ar eno spans', () => {
  129. expect(flattenFrames([])).toStrictEqual([]);
  130. });
  131. it('should return the FlattenedSpanRange for a single span', () => {
  132. const frames = hydrateSpans(TestStubs.ReplayRecord(), [
  133. TestStubs.Replay.RequestFrame({
  134. op: 'resource.fetch',
  135. startTimestamp: new Date(10000),
  136. endTimestamp: new Date(30000),
  137. }),
  138. ]);
  139. expect(flattenFrames(frames)).toStrictEqual([
  140. {
  141. duration: 20000,
  142. endTimestamp: 30000,
  143. frameCount: 1,
  144. startTimestamp: 10000,
  145. },
  146. ]);
  147. });
  148. it('should return two non-overlapping spans', () => {
  149. const frames = hydrateSpans(TestStubs.ReplayRecord(), [
  150. TestStubs.Replay.RequestFrame({
  151. op: 'resource.fetch',
  152. startTimestamp: new Date(10000),
  153. endTimestamp: new Date(30000),
  154. }),
  155. TestStubs.Replay.RequestFrame({
  156. op: 'resource.fetch',
  157. startTimestamp: new Date(60000),
  158. endTimestamp: new Date(90000),
  159. }),
  160. ]);
  161. expect(flattenFrames(frames)).toStrictEqual([
  162. {
  163. duration: 20000,
  164. endTimestamp: 30000,
  165. frameCount: 1,
  166. startTimestamp: 10000,
  167. },
  168. {
  169. duration: 30000,
  170. endTimestamp: 90000,
  171. frameCount: 1,
  172. startTimestamp: 60000,
  173. },
  174. ]);
  175. });
  176. it('should merge two overlapping spans', () => {
  177. const frames = hydrateSpans(TestStubs.ReplayRecord(), [
  178. TestStubs.Replay.RequestFrame({
  179. op: 'resource.fetch',
  180. startTimestamp: new Date(10000),
  181. endTimestamp: new Date(30000),
  182. }),
  183. TestStubs.Replay.RequestFrame({
  184. op: 'resource.fetch',
  185. startTimestamp: new Date(20000),
  186. endTimestamp: new Date(40000),
  187. }),
  188. ]);
  189. expect(flattenFrames(frames)).toStrictEqual([
  190. {
  191. duration: 30000,
  192. endTimestamp: 40000,
  193. frameCount: 2,
  194. startTimestamp: 10000,
  195. },
  196. ]);
  197. });
  198. it('should merge overlapping spans that are not first in the list', () => {
  199. const frames = hydrateSpans(TestStubs.ReplayRecord(), [
  200. TestStubs.Replay.RequestFrame({
  201. op: 'resource.fetch',
  202. startTimestamp: new Date(0),
  203. endTimestamp: new Date(1000),
  204. }),
  205. TestStubs.Replay.RequestFrame({
  206. op: 'resource.fetch',
  207. startTimestamp: new Date(10000),
  208. endTimestamp: new Date(30000),
  209. }),
  210. TestStubs.Replay.RequestFrame({
  211. op: 'resource.fetch',
  212. startTimestamp: new Date(20000),
  213. endTimestamp: new Date(40000),
  214. }),
  215. ]);
  216. expect(flattenFrames(frames)).toStrictEqual([
  217. {
  218. duration: 1000,
  219. endTimestamp: 1000,
  220. frameCount: 1,
  221. startTimestamp: 0,
  222. },
  223. {
  224. duration: 30000,
  225. endTimestamp: 40000,
  226. frameCount: 2,
  227. startTimestamp: 10000,
  228. },
  229. ]);
  230. });
  231. const diffMs = 1652309918676;
  232. describe('relativeTimeinMs', () => {
  233. it('returns relative time in MS', () => {
  234. expect(relativeTimeInMs('2022-05-11T23:04:27.576000Z', diffMs)).toEqual(348900);
  235. });
  236. it('returns invalid date if date string is malformed', () => {
  237. expect(relativeTimeInMs('202223:04:27.576000Z', diffMs)).toEqual(NaN);
  238. });
  239. });
  240. describe('showPlayerTime', () => {
  241. it('returns time formatted for player', () => {
  242. expect(showPlayerTime('2022-05-11T23:04:27.576000Z', diffMs)).toEqual('05:48');
  243. });
  244. it('returns 0:00 if timestamp is malformed', () => {
  245. expect(showPlayerTime('20223:04:27.576000Z', diffMs)).toEqual('00:00');
  246. });
  247. });
  248. describe('divide', () => {
  249. it('divides numbers safely', () => {
  250. expect(divide(81, 9)).toEqual(9);
  251. });
  252. it('dividing by zero returns zero', () => {
  253. expect(divide(81, 0)).toEqual(0);
  254. });
  255. });
  256. });