webVitalStatusLineChart.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import {useTheme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import ChartZoom from 'sentry/components/charts/chartZoom';
  4. import MarkArea from 'sentry/components/charts/components/markArea';
  5. import MarkLine from 'sentry/components/charts/components/markLine';
  6. import type {LineChartSeries} from 'sentry/components/charts/lineChart';
  7. import {LineChart} from 'sentry/components/charts/lineChart';
  8. import {getDuration} from 'sentry/utils/formatters';
  9. import usePageFilters from 'sentry/utils/usePageFilters';
  10. import useRouter from 'sentry/utils/useRouter';
  11. import {
  12. PERFORMANCE_SCORE_MEDIANS,
  13. PERFORMANCE_SCORE_P90S,
  14. } from 'sentry/views/performance/browser/webVitals/utils/queries/rawWebVitalsQueries/calculatePerformanceScore';
  15. interface Props {
  16. webVitalSeries: LineChartSeries;
  17. }
  18. export function WebVitalStatusLineChart({webVitalSeries}: Props) {
  19. const theme = useTheme();
  20. const router = useRouter();
  21. const pageFilters = usePageFilters();
  22. const {period, start, end, utc} = pageFilters.selection.datetime;
  23. const webVital = webVitalSeries.seriesName;
  24. const allSeries = [webVitalSeries];
  25. const showPoorMarkLine = webVitalSeries.data?.some(
  26. ({value}) => value > PERFORMANCE_SCORE_MEDIANS[webVital ?? '']
  27. );
  28. const showMehMarkLine = webVitalSeries.data?.some(
  29. ({value}) => value >= PERFORMANCE_SCORE_P90S[webVital ?? '']
  30. );
  31. const showGoodMarkLine = webVitalSeries.data?.every(
  32. ({value}) => value < PERFORMANCE_SCORE_P90S[webVital ?? '']
  33. );
  34. const goodMarkArea = MarkArea({
  35. silent: true,
  36. itemStyle: {
  37. color: theme.green300,
  38. opacity: 0.1,
  39. },
  40. data: [
  41. [
  42. {
  43. yAxis: PERFORMANCE_SCORE_P90S[webVital ?? ''],
  44. },
  45. {
  46. yAxis: 0,
  47. },
  48. ],
  49. ],
  50. });
  51. const mehMarkArea = MarkArea({
  52. silent: true,
  53. itemStyle: {
  54. color: theme.yellow300,
  55. opacity: 0.1,
  56. },
  57. data: [
  58. [
  59. {
  60. yAxis: PERFORMANCE_SCORE_MEDIANS[webVital ?? ''],
  61. },
  62. {
  63. yAxis: PERFORMANCE_SCORE_P90S[webVital ?? ''],
  64. },
  65. ],
  66. ],
  67. });
  68. const poorMarkArea = MarkArea({
  69. silent: true,
  70. itemStyle: {
  71. color: theme.red300,
  72. opacity: 0.1,
  73. },
  74. data: [
  75. [
  76. {
  77. yAxis: PERFORMANCE_SCORE_MEDIANS[webVital ?? ''],
  78. },
  79. {
  80. yAxis: Infinity,
  81. },
  82. ],
  83. ],
  84. });
  85. const goodMarkLine = MarkLine({
  86. silent: true,
  87. lineStyle: {
  88. color: theme.green300,
  89. },
  90. label: {
  91. formatter: () => 'Good',
  92. position: 'insideEndBottom',
  93. color: theme.green300,
  94. },
  95. data: showGoodMarkLine
  96. ? [
  97. [
  98. {xAxis: 'min', y: 10},
  99. {xAxis: 'max', y: 10},
  100. ],
  101. ]
  102. : [
  103. {
  104. yAxis: PERFORMANCE_SCORE_P90S[webVital ?? ''],
  105. },
  106. ],
  107. });
  108. const mehMarkLine = MarkLine({
  109. silent: true,
  110. lineStyle: {
  111. color: theme.yellow300,
  112. },
  113. label: {
  114. formatter: () => 'Meh',
  115. position: 'insideEndBottom',
  116. color: theme.yellow300,
  117. },
  118. data:
  119. showMehMarkLine && !showPoorMarkLine
  120. ? [
  121. [
  122. {xAxis: 'min', y: 10},
  123. {xAxis: 'max', y: 10},
  124. ],
  125. ]
  126. : [
  127. {
  128. yAxis: PERFORMANCE_SCORE_MEDIANS[webVital ?? ''],
  129. },
  130. ],
  131. });
  132. const poorMarkLine = MarkLine({
  133. silent: true,
  134. lineStyle: {
  135. color: theme.red300,
  136. },
  137. label: {
  138. formatter: () => 'Poor',
  139. position: 'insideEndBottom',
  140. color: theme.red300,
  141. },
  142. data: [
  143. [
  144. {xAxis: 'min', y: 10},
  145. {xAxis: 'max', y: 10},
  146. ],
  147. ],
  148. });
  149. allSeries.push({
  150. seriesName: '',
  151. type: 'line',
  152. markArea: goodMarkArea,
  153. data: [],
  154. });
  155. allSeries.push({
  156. seriesName: '',
  157. type: 'line',
  158. markArea: mehMarkArea,
  159. data: [],
  160. });
  161. allSeries.push({
  162. seriesName: '',
  163. type: 'line',
  164. markArea: poorMarkArea,
  165. data: [],
  166. });
  167. allSeries.push({
  168. seriesName: '',
  169. type: 'line',
  170. markLine: goodMarkLine,
  171. data: [],
  172. });
  173. allSeries.push({
  174. seriesName: '',
  175. type: 'line',
  176. markLine: mehMarkLine,
  177. data: [],
  178. });
  179. if (showPoorMarkLine) {
  180. allSeries.push({
  181. seriesName: '',
  182. type: 'line',
  183. markLine: poorMarkLine,
  184. data: [],
  185. });
  186. }
  187. const getFormattedDuration = (value: number) => {
  188. if (value < 1000) {
  189. return getDuration(value / 1000, 0, true);
  190. }
  191. return getDuration(value / 1000, 2, true);
  192. };
  193. return (
  194. <ChartContainer>
  195. {webVital && (
  196. <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
  197. {zoomRenderProps => (
  198. <LineChart
  199. {...zoomRenderProps}
  200. height={240}
  201. series={allSeries}
  202. xAxis={{show: false}}
  203. grid={{
  204. left: 0,
  205. right: 15,
  206. top: 10,
  207. bottom: 0,
  208. }}
  209. yAxis={
  210. webVital === 'cls' ? {} : {axisLabel: {formatter: getFormattedDuration}}
  211. }
  212. tooltip={webVital === 'cls' ? {} : {valueFormatter: getFormattedDuration}}
  213. />
  214. )}
  215. </ChartZoom>
  216. )}
  217. </ChartContainer>
  218. );
  219. }
  220. const ChartContainer = styled('div')`
  221. position: relative;
  222. flex: 1;
  223. `;