content.tsx 4.8 KB

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