webVitalStatusLineChart.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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/duration/getDuration';
  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/insights/browser/webVitals/utils/scoreThresholds';
  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 seriesIsPoor = webVitalSeries.data?.some(
  26. ({value}) => value > PERFORMANCE_SCORE_MEDIANS[webVital ?? '']
  27. );
  28. const seriesIsMeh = webVitalSeries.data?.some(
  29. ({value}) => value >= PERFORMANCE_SCORE_P90S[webVital ?? '']
  30. );
  31. const seriesIsGood = 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: [
  96. {
  97. yAxis: PERFORMANCE_SCORE_P90S[webVital ?? ''],
  98. },
  99. ],
  100. });
  101. const mehMarkLine = MarkLine({
  102. silent: true,
  103. lineStyle: {
  104. color: theme.yellow300,
  105. },
  106. label: {
  107. formatter: () => 'Meh',
  108. position: 'insideEndBottom',
  109. color: theme.yellow300,
  110. },
  111. data: [
  112. {
  113. yAxis: PERFORMANCE_SCORE_MEDIANS[webVital ?? ''],
  114. },
  115. ],
  116. });
  117. const poorMarkLine = MarkLine({
  118. silent: true,
  119. lineStyle: {
  120. color: theme.red300,
  121. },
  122. label: {
  123. formatter: () => 'Poor',
  124. position: 'insideEndBottom',
  125. color: theme.red300,
  126. },
  127. data: [
  128. [
  129. {xAxis: 'min', y: 10},
  130. {xAxis: 'max', y: 10},
  131. ],
  132. ],
  133. });
  134. allSeries.push({
  135. seriesName: '',
  136. type: 'line',
  137. markArea: goodMarkArea,
  138. data: [],
  139. });
  140. allSeries.push({
  141. seriesName: '',
  142. type: 'line',
  143. markArea: mehMarkArea,
  144. data: [],
  145. });
  146. allSeries.push({
  147. seriesName: '',
  148. type: 'line',
  149. markArea: poorMarkArea,
  150. data: [],
  151. });
  152. allSeries.push({
  153. seriesName: '',
  154. type: 'line',
  155. markLine: goodMarkLine,
  156. data: [],
  157. });
  158. allSeries.push({
  159. seriesName: '',
  160. type: 'line',
  161. markLine: mehMarkLine,
  162. data: [],
  163. });
  164. if (seriesIsPoor) {
  165. allSeries.push({
  166. seriesName: '',
  167. type: 'line',
  168. markLine: poorMarkLine,
  169. data: [],
  170. });
  171. }
  172. const getFormattedDuration = (value: number) => {
  173. if (value < 1000) {
  174. return getDuration(value / 1000, 0, true);
  175. }
  176. return getDuration(value / 1000, 2, true);
  177. };
  178. const getMaxYAxis = () => {
  179. if (seriesIsPoor) {
  180. return undefined;
  181. }
  182. if (seriesIsMeh) {
  183. return PERFORMANCE_SCORE_MEDIANS[webVital ?? ''];
  184. }
  185. if (seriesIsGood) {
  186. return PERFORMANCE_SCORE_P90S[webVital ?? ''];
  187. }
  188. return undefined;
  189. };
  190. const yAxisMax = getMaxYAxis();
  191. return (
  192. <ChartContainer>
  193. {webVital && (
  194. <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
  195. {zoomRenderProps => (
  196. <LineChart
  197. {...zoomRenderProps}
  198. height={240}
  199. series={allSeries}
  200. xAxis={{show: false}}
  201. grid={{
  202. left: 0,
  203. right: 15,
  204. top: 10,
  205. bottom: 0,
  206. }}
  207. yAxis={{
  208. ...(webVital === 'cls'
  209. ? {}
  210. : {axisLabel: {formatter: getFormattedDuration}}),
  211. max: yAxisMax,
  212. }}
  213. tooltip={webVital === 'cls' ? {} : {valueFormatter: getFormattedDuration}}
  214. />
  215. )}
  216. </ChartZoom>
  217. )}
  218. </ChartContainer>
  219. );
  220. }
  221. const ChartContainer = styled('div')`
  222. position: relative;
  223. flex: 1;
  224. `;