utils.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import {Crumb} from 'sentry/types/breadcrumbs';
  2. function padZero(num: number, len = 2): string {
  3. let str = String(num);
  4. const threshold = Math.pow(10, len - 1);
  5. if (num < threshold) {
  6. while (String(threshold).length > str.length) {
  7. str = '0' + num;
  8. }
  9. }
  10. return str;
  11. }
  12. const SECOND = 1000;
  13. const MINUTE = 60 * SECOND;
  14. const HOUR = 60 * MINUTE;
  15. // TODO: move into 'sentry/utils/formatters'
  16. export function formatTime(ms: number): string {
  17. if (ms <= 0) {
  18. return '0:00';
  19. }
  20. const hour = Math.floor(ms / HOUR);
  21. ms = ms % HOUR;
  22. const minute = Math.floor(ms / MINUTE);
  23. ms = ms % MINUTE;
  24. const second = Math.floor(ms / SECOND);
  25. if (hour) {
  26. return `${hour}:${padZero(minute)}:${padZero(second)}`;
  27. }
  28. return `${minute}:${padZero(second)}`;
  29. }
  30. /**
  31. * Figure out how many ticks to show in an area.
  32. * If there is more space available, we can show more granular ticks, but if
  33. * less space is available, fewer ticks.
  34. * Similarly if the duration is short, the ticks will represent a short amount
  35. * of time (like every second) but if the duration is long one tick may
  36. * represent an hour.
  37. *
  38. * @param duration The amount of time that we need to chop up into even sections
  39. * @param width Total width available, pixels
  40. * @param minWidth Minimum space for each column, pixels. Ex: So we can show formatted time like `1:00:00` between major ticks
  41. * @returns
  42. */
  43. export function countColumns(duration: number, width: number, minWidth: number = 50) {
  44. let maxCols = Math.floor(width / minWidth);
  45. const remainder = duration - maxCols * width > 0 ? 1 : 0;
  46. maxCols -= remainder;
  47. // List of all the possible time granularities to display
  48. // We could generate the list, which is basically a version of fizzbuzz, hard-coding is quicker.
  49. const timeOptions = [
  50. 1 * HOUR,
  51. 30 * MINUTE,
  52. 20 * MINUTE,
  53. 15 * MINUTE,
  54. 10 * MINUTE,
  55. 5 * MINUTE,
  56. 2 * MINUTE,
  57. 1 * MINUTE,
  58. 30 * SECOND,
  59. 10 * SECOND,
  60. 5 * SECOND,
  61. 1 * SECOND,
  62. ];
  63. const timeBasedCols = timeOptions.reduce<Map<number, number>>((map, time) => {
  64. map.set(time, Math.floor(duration / time));
  65. return map;
  66. }, new Map());
  67. const [timespan, cols] = Array.from(timeBasedCols.entries())
  68. .filter(([_span, c]) => c <= maxCols) // Filter for any valid timespan option where all ticks would fit
  69. .reduce((best, next) => (next[1] > best[1] ? next : best), [0, 0]); // select the timespan option with the most ticks
  70. const remaining = (duration - timespan * cols) / timespan;
  71. return {timespan, cols, remaining};
  72. }
  73. /**
  74. * Group Crumbs for display along the timeline.
  75. *
  76. * The timeline is broken down into columns (aka buckets, or time-slices).
  77. * Columns translate to a fixed width on the screen, to prevent side-scrolling.
  78. *
  79. * This function groups crumbs into columns based on the number of columns available
  80. * and the timestamp of the crumb.
  81. */
  82. export function getCrumbsByColumn(crumbs: Crumb[], totalColumns: number) {
  83. const startTime = crumbs[0]?.timestamp;
  84. const endTime = crumbs[crumbs.length - 1]?.timestamp;
  85. // If there is only one crumb then we cannot do the math, return it in the first column
  86. if (crumbs.length === 1 || startTime === endTime) {
  87. return new Map([[0, crumbs]]);
  88. }
  89. const startMilliSeconds = +new Date(String(startTime));
  90. const endMilliSeconds = +new Date(String(endTime));
  91. const duration = endMilliSeconds - startMilliSeconds;
  92. const safeDuration = isNaN(duration) ? 1 : duration;
  93. const columnCrumbPairs = crumbs.map(breadcrumb => {
  94. const {timestamp} = breadcrumb;
  95. const timestampMilliSeconds = +new Date(String(timestamp));
  96. const sinceStart = isNaN(timestampMilliSeconds)
  97. ? 0
  98. : timestampMilliSeconds - startMilliSeconds;
  99. const column = Math.floor((sinceStart / safeDuration) * (totalColumns - 1)) + 1;
  100. return [column, breadcrumb] as [number, Crumb];
  101. });
  102. const crumbsByColumn = columnCrumbPairs.reduce((map, [column, breadcrumb]) => {
  103. if (map.has(column)) {
  104. map.get(column)?.push(breadcrumb);
  105. } else {
  106. map.set(column, [breadcrumb]);
  107. }
  108. return map;
  109. }, new Map() as Map<number, Crumb[]>);
  110. return crumbsByColumn;
  111. }