getIntervalLine.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. // NOTE: Position is changed from insideStartBottom to insideStartTop to check if chartcuterie deployments are working
  150. position: 'insideStartTop',
  151. };
  152. periodDividingLine.markLine.lineStyle = {
  153. ...periodDividingLine.markLine.lineStyle,
  154. color: theme.red300,
  155. };
  156. currentPeriod.markLine.lineStyle = {
  157. ...currentPeriod.markLine.lineStyle,
  158. color: theme.red300,
  159. };
  160. currentPeriod.markLine.label = {
  161. ...periodLineLabel,
  162. formatter: `Regressed ${getPerformanceDuration(
  163. transformedTransaction.aggregate_range_2
  164. )}`,
  165. position: 'insideEndTop',
  166. color: theme.gray400,
  167. };
  168. additionalLineSeries.push({
  169. seriesName: 'Regression Area',
  170. markLine: {},
  171. markArea: MarkArea({
  172. silent: true,
  173. itemStyle: {
  174. color: theme.red300,
  175. opacity: 0.2,
  176. },
  177. data: [
  178. [
  179. {
  180. xAxis: divider,
  181. },
  182. {xAxis: seriesEnd},
  183. ],
  184. ],
  185. }),
  186. data: [],
  187. });
  188. additionalLineSeries.push({
  189. seriesName: 'Baseline Axis Line',
  190. type: 'line',
  191. markLine:
  192. MarkLine({
  193. silent: true,
  194. label: {
  195. show: false,
  196. },
  197. lineStyle: {color: theme.green400, type: 'solid', width: 4},
  198. data: [
  199. // The line needs to be hard-coded to a pixel coordinate because
  200. // the lowest y-value is dynamic and 'min' doesn't work here
  201. [
  202. {xAxis: 'min', y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  203. {xAxis: breakpoint, y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  204. ],
  205. ],
  206. }) ?? {},
  207. data: [],
  208. });
  209. additionalLineSeries.push({
  210. seriesName: 'Regression Axis Line',
  211. type: 'line',
  212. markLine:
  213. MarkLine({
  214. silent: true,
  215. label: {
  216. show: false,
  217. },
  218. lineStyle: {color: theme.red300, type: 'solid', width: 4},
  219. data: [
  220. // The line needs to be hard-coded to a pixel coordinate because
  221. // the lowest y-value is dynamic and 'min' doesn't work here
  222. [
  223. {xAxis: breakpoint, y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  224. {xAxis: 'max', y: DEFAULT_CHART_HEIGHT - X_AXIS_MARGIN_OFFSET},
  225. ],
  226. ],
  227. }) ?? {},
  228. data: [],
  229. });
  230. }
  231. return additionalLineSeries;
  232. }