content.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import {useContext, useEffect} from 'react';
  2. import type {InjectedRouter} from 'react-router';
  3. import type {Theme} from '@emotion/react';
  4. import type {Query} from 'history';
  5. import {AreaChart} from 'sentry/components/charts/areaChart';
  6. import ChartZoom from 'sentry/components/charts/chartZoom';
  7. import ErrorPanel from 'sentry/components/charts/errorPanel';
  8. import type {LineChartProps} from 'sentry/components/charts/lineChart';
  9. import ReleaseSeries from 'sentry/components/charts/releaseSeries';
  10. import TransitionChart from 'sentry/components/charts/transitionChart';
  11. import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask';
  12. import Placeholder from 'sentry/components/placeholder';
  13. import {IconWarning} from 'sentry/icons';
  14. import type {Series} from 'sentry/types/echarts';
  15. import {
  16. axisLabelFormatter,
  17. getDurationUnit,
  18. tooltipFormatter,
  19. } from 'sentry/utils/discover/charts';
  20. import getDynamicText from 'sentry/utils/getDynamicText';
  21. import {PerformanceAtScaleContext} from 'sentry/views/performance/transactionSummary/transactionOverview/performanceAtScaleContext';
  22. type Props = {
  23. errored: boolean;
  24. loading: boolean;
  25. queryExtra: Query;
  26. reloading: boolean;
  27. router: InjectedRouter;
  28. theme: Theme;
  29. series?: Series[];
  30. timeFrame?: {
  31. end: number;
  32. start: number;
  33. };
  34. } & Omit<React.ComponentProps<typeof ReleaseSeries>, 'children' | 'queryExtra'> &
  35. Pick<LineChartProps, 'onLegendSelectChanged' | 'legend'>;
  36. function Content({
  37. errored,
  38. theme,
  39. series: data,
  40. timeFrame,
  41. start,
  42. end,
  43. period,
  44. projects,
  45. environments,
  46. loading,
  47. reloading,
  48. legend,
  49. utc,
  50. queryExtra,
  51. router,
  52. onLegendSelectChanged,
  53. }: Props) {
  54. const performanceAtScaleContext = useContext(PerformanceAtScaleContext);
  55. const isSeriesDataEmpty = data?.every(values => {
  56. return values.data.every(value => !value.value);
  57. });
  58. useEffect(() => {
  59. if (!performanceAtScaleContext || isSeriesDataEmpty === undefined) {
  60. return;
  61. }
  62. if (loading || reloading) {
  63. performanceAtScaleContext.setMetricsSeriesDataEmpty(undefined);
  64. return;
  65. }
  66. performanceAtScaleContext.setMetricsSeriesDataEmpty(isSeriesDataEmpty);
  67. }, [loading, reloading, isSeriesDataEmpty, performanceAtScaleContext]);
  68. if (errored) {
  69. return (
  70. <ErrorPanel>
  71. <IconWarning color="gray500" size="lg" />
  72. </ErrorPanel>
  73. );
  74. }
  75. const colors = (data && theme.charts.getColorPalette(data.length - 2)) || [];
  76. // Create a list of series based on the order of the fields,
  77. // We need to flip it at the end to ensure the series stack right.
  78. const series = data
  79. ? data
  80. .map((values, i: number) => {
  81. return {
  82. ...values,
  83. color: colors[i],
  84. lineStyle: {
  85. opacity: 0,
  86. },
  87. };
  88. })
  89. .reverse()
  90. : [];
  91. const durationUnit = getDurationUnit(series, legend);
  92. const chartOptions = {
  93. grid: {
  94. left: '10px',
  95. right: '10px',
  96. top: '40px',
  97. bottom: '0px',
  98. },
  99. seriesOptions: {
  100. showSymbol: false,
  101. },
  102. tooltip: {
  103. trigger: 'axis' as const,
  104. valueFormatter: (value, _label) => tooltipFormatter(value, 'duration'),
  105. },
  106. xAxis: timeFrame
  107. ? {
  108. min: timeFrame.start,
  109. max: timeFrame.end,
  110. }
  111. : undefined,
  112. yAxis: {
  113. minInterval: durationUnit,
  114. axisLabel: {
  115. color: theme.chartLabel,
  116. formatter: (value: number) => {
  117. return axisLabelFormatter(value, 'duration', undefined, durationUnit);
  118. },
  119. },
  120. },
  121. };
  122. return (
  123. <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
  124. {zoomRenderProps => (
  125. <ReleaseSeries
  126. start={start}
  127. end={end}
  128. queryExtra={queryExtra}
  129. period={period}
  130. utc={utc}
  131. projects={projects}
  132. environments={environments}
  133. >
  134. {({releaseSeries}) => {
  135. return (
  136. <TransitionChart loading={loading} reloading={reloading}>
  137. <TransparentLoadingMask visible={reloading} />
  138. {getDynamicText({
  139. value: (
  140. <AreaChart
  141. {...zoomRenderProps}
  142. {...chartOptions}
  143. legend={legend}
  144. onLegendSelectChanged={onLegendSelectChanged}
  145. series={[...series, ...releaseSeries]}
  146. />
  147. ),
  148. fixed: <Placeholder height="200px" testId="skeleton-ui" />,
  149. })}
  150. </TransitionChart>
  151. );
  152. }}
  153. </ReleaseSeries>
  154. )}
  155. </ChartZoom>
  156. );
  157. }
  158. export default Content;