exclusiveTimeHistogram.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import {Fragment} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import {BarChart} from 'sentry/components/charts/barChart';
  4. import BarChartZoom from 'sentry/components/charts/barChartZoom';
  5. import ErrorPanel from 'sentry/components/charts/errorPanel';
  6. import {HeaderTitleLegend} from 'sentry/components/charts/styles';
  7. import TransitionChart from 'sentry/components/charts/transitionChart';
  8. import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask';
  9. import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
  10. import Placeholder from 'sentry/components/placeholder';
  11. import QuestionTooltip from 'sentry/components/questionTooltip';
  12. import {IconWarning} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import {Organization} from 'sentry/types';
  15. import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
  16. import EventView from 'sentry/utils/discover/eventView';
  17. import {aggregateOutputType} from 'sentry/utils/discover/fields';
  18. import getDynamicText from 'sentry/utils/getDynamicText';
  19. import SpanHistogramQuery from 'sentry/utils/performance/histogram/spanHistogramQuery';
  20. import {HistogramData} from 'sentry/utils/performance/histogram/types';
  21. import {
  22. computeBuckets,
  23. formatHistogramData,
  24. } from 'sentry/utils/performance/histogram/utils';
  25. import {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
  26. import {decodeScalar} from 'sentry/utils/queryString';
  27. import {useLocation} from 'sentry/utils/useLocation';
  28. import {ZoomKeys} from './utils';
  29. const NUM_BUCKETS = 50;
  30. const PRECISION = 0;
  31. type Props = {
  32. eventView: EventView;
  33. organization: Organization;
  34. spanSlug: SpanSlug;
  35. };
  36. export default function ExclusiveTimeHistogram(props: Props) {
  37. const {organization, eventView, spanSlug} = props;
  38. const location = useLocation();
  39. const start = decodeScalar(location.query[ZoomKeys.MIN]);
  40. const end = decodeScalar(location.query[ZoomKeys.MAX]);
  41. return (
  42. <Fragment>
  43. <HeaderTitleLegend>
  44. {t('Self Time Distribution')}
  45. <QuestionTooltip
  46. size="sm"
  47. position="top"
  48. title={t(
  49. 'Distribution buckets counts of the same self time duration for the selected span op and group.'
  50. )}
  51. />
  52. </HeaderTitleLegend>
  53. <SpanHistogramQuery
  54. location={location}
  55. orgSlug={organization.slug}
  56. eventView={eventView}
  57. numBuckets={NUM_BUCKETS}
  58. precision={PRECISION}
  59. span={spanSlug}
  60. dataFilter="exclude_outliers"
  61. min={start}
  62. max={end}
  63. >
  64. {({histogram, isLoading, error}) => {
  65. if (error) {
  66. return (
  67. <ErrorPanel data-test-id="histogram-error-panel">
  68. <IconWarning color="gray300" size="lg" />
  69. </ErrorPanel>
  70. );
  71. }
  72. return (
  73. <TransitionChart loading={isLoading} reloading={isLoading}>
  74. <TransparentLoadingMask visible={isLoading} />
  75. <BarChartZoom
  76. minZoomWidth={1}
  77. location={location}
  78. paramStart={ZoomKeys.MIN}
  79. paramEnd={ZoomKeys.MAX}
  80. xAxisIndex={[0]}
  81. buckets={histogram ? computeBuckets(histogram) : []}
  82. >
  83. {zoomRenderProps => (
  84. <Chart
  85. zoomProps={{...zoomRenderProps}}
  86. isLoading={isLoading}
  87. isErrored={!!error}
  88. chartData={histogram}
  89. spanSlug={spanSlug}
  90. />
  91. )}
  92. </BarChartZoom>
  93. </TransitionChart>
  94. );
  95. }}
  96. </SpanHistogramQuery>
  97. </Fragment>
  98. );
  99. }
  100. type ChartProps = {
  101. chartData: HistogramData | null;
  102. isErrored: boolean;
  103. isLoading: boolean;
  104. spanSlug: SpanSlug;
  105. zoomProps: any;
  106. disableChartPadding?: boolean;
  107. };
  108. export function Chart(props: ChartProps) {
  109. const theme = useTheme();
  110. const {chartData, zoomProps, spanSlug} = props;
  111. if (!chartData) {
  112. return <Placeholder height="200px" />;
  113. }
  114. const chartOptions = {
  115. grid: {
  116. left: '10px',
  117. right: '10px',
  118. top: '40px',
  119. bottom: '0px',
  120. },
  121. colors: () => pickBarColor(spanSlug.op),
  122. seriesOptions: {
  123. showSymbol: false,
  124. },
  125. tooltip: {
  126. trigger: 'axis' as const,
  127. // TODO (udameli) pull series name from the meta
  128. valueFormatter: (value, _seriesName) =>
  129. tooltipFormatter(value, aggregateOutputType(_seriesName)),
  130. },
  131. yAxis: {
  132. type: 'value' as const,
  133. axisLabel: {
  134. color: theme.chartLabel,
  135. formatter: (value: number) => axisLabelFormatter(value, 'number'),
  136. },
  137. },
  138. xAxis: {
  139. type: 'category' as const,
  140. truncate: true,
  141. axisTick: {
  142. alignWithLabel: true,
  143. },
  144. },
  145. height: 200,
  146. };
  147. const series = {
  148. seriesName: t('Count'),
  149. data: formatHistogramData(chartData, {type: 'duration'}),
  150. };
  151. return (
  152. <Fragment>
  153. {getDynamicText({
  154. value: <BarChart {...zoomProps} {...chartOptions} series={[series]} stacked />,
  155. fixed: <Placeholder height="200px" />,
  156. })}
  157. </Fragment>
  158. );
  159. }