getIntervalLine.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import type {Theme} from '@emotion/react';
  2. import MarkArea from 'sentry/components/charts/components/markArea';
  3. import MarkLine from 'sentry/components/charts/components/markLine';
  4. import type {LineChartSeries} from 'sentry/components/charts/lineChart';
  5. import {t} from 'sentry/locale';
  6. import type {Series} from 'sentry/types/echarts';
  7. import {tooltipFormatter} from 'sentry/utils/discover/charts';
  8. import type {NormalizedTrendsTransaction} from 'sentry/views/performance/trends/types';
  9. import {getPerformanceDuration} from 'sentry/views/performance/utils/getPerformanceDuration';
  10. import transformTransaction from 'sentry/views/performance/utils/transformTransaction';
  11. const DEFAULT_CHART_HEIGHT = 200;
  12. const X_AXIS_MARGIN_OFFSET = 23;
  13. export function getIntervalLine(
  14. theme: Theme,
  15. series: Series[],
  16. intervalRatio: number,
  17. label: boolean,
  18. transaction?: NormalizedTrendsTransaction,
  19. useRegressionFormat?: boolean
  20. ): LineChartSeries[] {
  21. if (!transaction || !series.length || !series[0].data || !series[0].data.length) {
  22. return [];
  23. }
  24. const transformedTransaction = transformTransaction(transaction);
  25. const seriesStart = parseInt(series[0].data[0].name as string, 10);
  26. const seriesEnd = parseInt(series[0].data.slice(-1)[0].name as string, 10);
  27. if (seriesEnd < seriesStart) {
  28. return [];
  29. }
  30. const periodLine: LineChartSeries = {
  31. data: [],
  32. color: theme.textColor,
  33. markLine: {
  34. data: [],
  35. label: {},
  36. lineStyle: {
  37. color: theme.textColor,
  38. type: 'dashed',
  39. width: label ? 1 : 2,
  40. },
  41. symbol: ['none', 'none'],
  42. tooltip: {
  43. show: false,
  44. },
  45. },
  46. seriesName: 'Baseline',
  47. };
  48. const periodLineLabel = {
  49. fontSize: 11,
  50. show: label,
  51. color: theme.textColor,
  52. silent: label,
  53. };
  54. const previousPeriod = {
  55. ...periodLine,
  56. markLine: {...periodLine.markLine},
  57. seriesName: 'Baseline',
  58. };
  59. const currentPeriod = {
  60. ...periodLine,
  61. markLine: {...periodLine.markLine},
  62. seriesName: 'Baseline',
  63. };
  64. const periodDividingLine = {
  65. ...periodLine,
  66. markLine: {...periodLine.markLine},
  67. seriesName: 'Baseline',
  68. };
  69. const seriesDiff = seriesEnd - seriesStart;
  70. const seriesLine = seriesDiff * intervalRatio + seriesStart;
  71. const {breakpoint} = transformedTransaction;
  72. const divider = breakpoint || seriesLine;
  73. previousPeriod.markLine.data = [
  74. [
  75. {value: 'Past', coord: [seriesStart, transformedTransaction.aggregate_range_1]},
  76. {coord: [divider, transformedTransaction.aggregate_range_1]},
  77. ],
  78. ];
  79. previousPeriod.markLine.tooltip = {
  80. formatter: () => {
  81. return [
  82. '<div class="tooltip-series tooltip-series-solo">',
  83. '<div>',
  84. `<span class="tooltip-label"><strong>${t('Past Baseline')}</strong></span>`,
  85. // p50() coerces the axis to be time based
  86. tooltipFormatter(transformedTransaction.aggregate_range_1, 'duration'),
  87. '</div>',
  88. '</div>',
  89. '<div class="tooltip-arrow"></div>',
  90. ].join('');
  91. },
  92. };
  93. currentPeriod.markLine.data = [
  94. [
  95. {value: 'Present', coord: [divider, transformedTransaction.aggregate_range_2]},
  96. {coord: [seriesEnd, transformedTransaction.aggregate_range_2]},
  97. ],
  98. ];
  99. currentPeriod.markLine.tooltip = {
  100. formatter: () => {
  101. return [
  102. '<div class="tooltip-series tooltip-series-solo">',
  103. '<div>',
  104. `<span class="tooltip-label"><strong>${t('Present Baseline')}</strong></span>`,
  105. // p50() coerces the axis to be time based
  106. tooltipFormatter(transformedTransaction.aggregate_range_2, 'duration'),
  107. '</div>',
  108. '</div>',
  109. '<div class="tooltip-arrow"></div>',
  110. ].join('');
  111. },
  112. };
  113. periodDividingLine.markLine = {
  114. data: [
  115. {
  116. xAxis: divider,
  117. },
  118. ],
  119. label: {show: false},
  120. lineStyle: {
  121. color: theme.textColor,
  122. type: 'solid',
  123. width: 2,
  124. },
  125. symbol: ['none', 'none'],
  126. tooltip: {
  127. show: false,
  128. },
  129. silent: true,
  130. };
  131. previousPeriod.markLine.label = {
  132. ...periodLineLabel,
  133. formatter: 'Past',
  134. position: 'insideStartBottom',
  135. };
  136. currentPeriod.markLine.label = {
  137. ...periodLineLabel,
  138. formatter: 'Present',
  139. position: 'insideEndBottom',
  140. };
  141. const additionalLineSeries = [previousPeriod, currentPeriod, periodDividingLine];
  142. // Apply new styles for statistical detector regression issue
  143. if (useRegressionFormat) {
  144. previousPeriod.markLine.label = {
  145. ...periodLineLabel,
  146. formatter: `Baseline ${getPerformanceDuration(
  147. transformedTransaction.aggregate_range_1
  148. )}`,
  149. position: 'insideStartBottom',
  150. };
  151. periodDividingLine.markLine.lineStyle = {
  152. ...periodDividingLine.markLine.lineStyle,
  153. color: theme.red300,
  154. };
  155. currentPeriod.markLine.lineStyle = {
  156. ...currentPeriod.markLine.lineStyle,
  157. color: theme.red300,
  158. };
  159. currentPeriod.markLine.label = {
  160. ...periodLineLabel,
  161. formatter: `Regressed ${getPerformanceDuration(
  162. transformedTransaction.aggregate_range_2
  163. )}`,
  164. position: 'insideEndBottom',
  165. color: theme.red300,
  166. };
  167. additionalLineSeries.push({
  168. seriesName: 'Regression Area',
  169. markLine: {},
  170. markArea: MarkArea({
  171. silent: true,
  172. itemStyle: {
  173. color: theme.red300,
  174. opacity: 0.2,
  175. },
  176. data: [
  177. [
  178. {
  179. xAxis: divider,
  180. },
  181. {xAxis: seriesEnd},
  182. ],
  183. ],
  184. }),
  185. data: [],
  186. });
  187. additionalLineSeries.push({
  188. seriesName: 'Baseline Axis Line',
  189. type: 'line',
  190. markLine:
  191. MarkLine({
  192. silent: true,
  193. label: {
  194. show: false,
  195. },
  196. lineStyle: {color: theme.green400, type: 'solid', width: 4},
  197. data: [
  198. // The line needs to be hard-coded to a pixel coordinate because
  199. // the lowest y-value is dynamic and 'min' doesn't work here
  200. [
  201. {xAxis: 'min', y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  202. {xAxis: breakpoint, y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  203. ],
  204. ],
  205. }) ?? {},
  206. data: [],
  207. });
  208. additionalLineSeries.push({
  209. seriesName: 'Regression Axis Line',
  210. type: 'line',
  211. markLine:
  212. MarkLine({
  213. silent: true,
  214. label: {
  215. show: false,
  216. },
  217. lineStyle: {color: theme.red300, type: 'solid', width: 4},
  218. data: [
  219. // The line needs to be hard-coded to a pixel coordinate because
  220. // the lowest y-value is dynamic and 'min' doesn't work here
  221. [
  222. {xAxis: breakpoint, y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  223. {xAxis: 'max', y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  224. ],
  225. ],
  226. }) ?? {},
  227. data: [],
  228. });
  229. }
  230. return additionalLineSeries;
  231. }