content.tsx 4.6 KB

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