exclusiveTimeHistogram.tsx 5.2 KB

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