webVitalStatusLineChart.tsx 5.2 KB

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