charts.spec.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {LegendComponentOption} from 'echarts';
  2. import {Series} from 'sentry/types/echarts';
  3. import {
  4. axisLabelFormatter,
  5. categorizeDuration,
  6. findRangeOfMultiSeries,
  7. getDurationUnit,
  8. tooltipFormatter,
  9. } from 'sentry/utils/discover/charts';
  10. import {HOUR, MINUTE, SECOND} from 'sentry/utils/formatters';
  11. describe('tooltipFormatter()', () => {
  12. it('formats values', () => {
  13. const cases: [string, number, string][] = [
  14. // function, input, expected
  15. ['count()', 0.1, '0.1'],
  16. ['avg(thing)', 0.125126, '0.125'],
  17. ['failure_rate()', 0.66123, '66.12%'],
  18. ['p50()', 100, '100.00ms'],
  19. ['p50()', 100.23, '100.23ms'],
  20. ['p50()', 1200, '1.20s'],
  21. ['p50()', 86400000, '1.00d'],
  22. ];
  23. for (const scenario of cases) {
  24. expect(tooltipFormatter(scenario[1], scenario[0])).toEqual(scenario[2]);
  25. }
  26. });
  27. });
  28. describe('axisLabelFormatter()', () => {
  29. it('formats values', () => {
  30. const cases: [string, number, string][] = [
  31. // type, input, expected
  32. ['count()', 0.1, '0.1'],
  33. ['avg(thing)', 0.125126, '0.125'],
  34. ['failure_rate()', 0.66123, '66%'],
  35. ['p50()', 100, '100ms'],
  36. ['p50()', 541, '541ms'],
  37. ['p50()', 1200, '1s'],
  38. ['p50()', 60000, '1min'],
  39. ['p50()', 120000, '2min'],
  40. ['p50()', 3600000, '1hr'],
  41. ['p50()', 86400000, '1d'],
  42. ];
  43. for (const scenario of cases) {
  44. expect(axisLabelFormatter(scenario[1], scenario[0])).toEqual(scenario[2]);
  45. }
  46. });
  47. describe('When a duration unit is passed', () => {
  48. const getAxisLabels = (axisValues: number[], durationUnit: number) => {
  49. return axisValues.map(value =>
  50. axisLabelFormatter(value, 'p50()', undefined, durationUnit)
  51. );
  52. };
  53. const generateDurationUnit = (axisValues: number[]) => {
  54. const max = Math.max(...axisValues);
  55. const min = Math.min(...axisValues);
  56. return categorizeDuration((max + min) * 0.5);
  57. };
  58. it('should not contain duplicate axis labels', () => {
  59. const axisValues = [40 * SECOND, 50 * SECOND, 60 * SECOND, 70 * SECOND];
  60. const durationUnit = generateDurationUnit(axisValues);
  61. const labels = getAxisLabels(axisValues, durationUnit);
  62. expect(labels.length).toBe(new Set(labels).size);
  63. });
  64. it('should use the same duration unit', () => {
  65. const axisValues = [50 * MINUTE, 150 * MINUTE, 250 * MINUTE, 350 * MINUTE];
  66. const durationUnit = generateDurationUnit(axisValues);
  67. const labels = getAxisLabels(axisValues, durationUnit);
  68. expect(labels.length).toBe(labels.filter(label => label.endsWith('hr')).length);
  69. });
  70. });
  71. });
  72. describe('findRangeOfMultiSeries()', () => {
  73. const series: Series[] = [
  74. {
  75. seriesName: 'p100()',
  76. data: [
  77. {name: 1, value: 2300},
  78. {name: 2, value: 1900},
  79. {name: 3, value: 1950},
  80. ],
  81. },
  82. {
  83. seriesName: 'p95()',
  84. data: [
  85. {name: 1, value: 300},
  86. {name: 2, value: 280},
  87. {name: 3, value: 290},
  88. ],
  89. },
  90. {
  91. seriesName: 'p50()',
  92. data: [
  93. {name: 1, value: 100},
  94. {name: 2, value: 50},
  95. {name: 3, value: 80},
  96. ],
  97. },
  98. ];
  99. it('should find min and max when no items selected in legend', () => {
  100. expect(findRangeOfMultiSeries(series)).toStrictEqual({max: 2300, min: 50});
  101. });
  102. it('should not find range if no items selected', () => {
  103. const legend: LegendComponentOption = {
  104. selected: {'p100()': false, 'p95()': false, 'p50()': false},
  105. };
  106. expect(findRangeOfMultiSeries(series, legend)).toStrictEqual(undefined);
  107. });
  108. it('should ignore p100 series if not selected', () => {
  109. const legend: LegendComponentOption = {
  110. selected: {'p100()': false},
  111. };
  112. expect(findRangeOfMultiSeries(series, legend)).toStrictEqual({max: 300, min: 50});
  113. });
  114. it('should ignore p50 series if not selected', () => {
  115. const legend: LegendComponentOption = {
  116. selected: {'p50()': false},
  117. };
  118. expect(findRangeOfMultiSeries(series, legend)).toStrictEqual({max: 2300, min: 280});
  119. });
  120. it('should display p100 value if selected and in legend object', () => {
  121. const legend: LegendComponentOption = {
  122. selected: {'p100()': true},
  123. };
  124. expect(findRangeOfMultiSeries(series, legend)).toStrictEqual({max: 2300, min: 50});
  125. });
  126. });
  127. describe('getDurationUnit()', () => {
  128. const MILLISECOND = 1;
  129. const generateSeries = (axisValues: number[]): Series[] => {
  130. return [
  131. {
  132. seriesName: 'p100()',
  133. data: axisValues.map((val, idx) => ({name: idx, value: val})),
  134. },
  135. ];
  136. };
  137. it('should return ms during transtion between ms to s', () => {
  138. const series = generateSeries([700, 800, 900, SECOND, 1.1 * SECOND]);
  139. expect(getDurationUnit(series)).toBe(MILLISECOND);
  140. });
  141. it('should return s during transtion between s to min', () => {
  142. const series = generateSeries([40 * SECOND, 50 * SECOND, MINUTE, 1.3 * MINUTE]);
  143. expect(getDurationUnit(series)).toBe(SECOND);
  144. });
  145. it('should return ms if y range is small', () => {
  146. const series = generateSeries([1000, 1050, 1100, 1150, 1200]);
  147. expect(getDurationUnit(series)).toBe(MILLISECOND);
  148. });
  149. it('should return min if yAxis range >= 5 min', () => {
  150. const series = generateSeries([1 * MINUTE, 2 * MINUTE, 4 * MINUTE, 6 * MINUTE]);
  151. expect(getDurationUnit(series)).toBe(MINUTE);
  152. });
  153. it('should return sec if yAxis range < 5 min', () => {
  154. const series = generateSeries([1 * MINUTE, 2 * MINUTE, 4 * MINUTE, 5 * MINUTE]);
  155. expect(getDurationUnit(series)).toBe(SECOND);
  156. });
  157. it('should use second with ms yAxis range if label length is long', () => {
  158. const series = generateSeries([4 * HOUR, 4.0001 * HOUR, 4.0002 * HOUR]);
  159. const durationUnit = getDurationUnit(series);
  160. const numOfDigits = ((4.0001 * HOUR) / durationUnit).toFixed(0).length;
  161. expect(numOfDigits).toBeLessThan(6);
  162. expect(durationUnit).not.toBe(MILLISECOND);
  163. });
  164. });