index.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import {Fragment} from 'react';
  2. import {browserHistory, withRouter, WithRouterProps} from 'react-router';
  3. import {useTheme} from '@emotion/react';
  4. import {Location, Query} from 'history';
  5. import ErrorPanel from 'sentry/components/charts/errorPanel';
  6. import EventsRequest from 'sentry/components/charts/eventsRequest';
  7. import {HeaderTitleLegend} from 'sentry/components/charts/styles';
  8. import {getInterval, getSeriesSelection} from 'sentry/components/charts/utils';
  9. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  10. import QuestionTooltip from 'sentry/components/questionTooltip';
  11. import {t, tct} from 'sentry/locale';
  12. import {OrganizationSummary} from 'sentry/types';
  13. import {getUtcToLocalDateObject} from 'sentry/utils/dates';
  14. import {TransactionMetric} from 'sentry/utils/metrics/fields';
  15. import MetricsRequest from 'sentry/utils/metrics/metricsRequest';
  16. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  17. import useApi from 'sentry/utils/useApi';
  18. import {useMetricsSwitch} from 'sentry/views/performance/metricsSwitch';
  19. import {transformMetricsToArea} from '../../../landing/widgets/transforms/transformMetricsToArea';
  20. import {ViewProps} from '../../../types';
  21. import {
  22. SPAN_OPERATION_BREAKDOWN_FILTER_TO_FIELD,
  23. SpanOperationBreakdownFilter,
  24. } from '../../filter';
  25. import Content from './content';
  26. type Props = WithRouterProps &
  27. ViewProps & {
  28. currentFilter: SpanOperationBreakdownFilter;
  29. location: Location;
  30. organization: OrganizationSummary;
  31. queryExtra: Query;
  32. withoutZerofill: boolean;
  33. };
  34. enum DurationFunctionField {
  35. P50 = 'p50',
  36. P75 = 'p75',
  37. P95 = 'p95',
  38. P99 = 'p99',
  39. p100 = 'p100',
  40. }
  41. /**
  42. * Fetch and render a stacked area chart that shows duration percentiles over
  43. * the past 7 days
  44. */
  45. function DurationChart({
  46. project,
  47. environment,
  48. location,
  49. organization,
  50. query,
  51. statsPeriod,
  52. router,
  53. queryExtra,
  54. currentFilter,
  55. withoutZerofill,
  56. start: propsStart,
  57. end: propsEnd,
  58. }: Props) {
  59. const api = useApi();
  60. const theme = useTheme();
  61. const {isMetricsData} = useMetricsSwitch();
  62. function handleLegendSelectChanged(legendChange: {
  63. name: string;
  64. selected: Record<string, boolean>;
  65. type: string;
  66. }) {
  67. const {selected} = legendChange;
  68. const unselected = Object.keys(selected).filter(key => !selected[key]);
  69. const to = {
  70. ...location,
  71. query: {
  72. ...location.query,
  73. unselectedSeries: unselected,
  74. },
  75. };
  76. browserHistory.push(to);
  77. }
  78. const start = propsStart ? getUtcToLocalDateObject(propsStart) : null;
  79. const end = propsEnd ? getUtcToLocalDateObject(propsEnd) : null;
  80. const utc = normalizeDateTimeParams(location.query).utc === 'true';
  81. const period = statsPeriod;
  82. const legend = {right: 10, top: 5, selected: getSeriesSelection(location)};
  83. const datetimeSelection = {start, end, period};
  84. const contentCommonProps = {
  85. theme,
  86. router,
  87. start,
  88. end,
  89. utc,
  90. legend,
  91. queryExtra,
  92. period,
  93. projects: project,
  94. environments: environment,
  95. onLegendSelectChanged: handleLegendSelectChanged,
  96. };
  97. const requestCommonProps = {
  98. api,
  99. start,
  100. end,
  101. project,
  102. environment,
  103. query,
  104. period,
  105. interval: getInterval(datetimeSelection, 'high'),
  106. };
  107. const parameter = SPAN_OPERATION_BREAKDOWN_FILTER_TO_FIELD[currentFilter] ?? '';
  108. const header = (
  109. <HeaderTitleLegend>
  110. {currentFilter === SpanOperationBreakdownFilter.None
  111. ? t('Duration Breakdown')
  112. : tct('Span Operation Breakdown - [operationName]', {
  113. operationName: currentFilter,
  114. })}
  115. <QuestionTooltip
  116. size="sm"
  117. position="top"
  118. title={t(
  119. `Duration Breakdown reflects transaction durations by percentile over time.`
  120. )}
  121. />
  122. </HeaderTitleLegend>
  123. );
  124. if (isMetricsData) {
  125. // TODO(metrics): Update this logic as soon as the metrics API supports spans
  126. if (!!parameter) {
  127. return (
  128. <Fragment>
  129. {header}
  130. <ErrorPanel>{`TODO: P* ${parameter}`}</ErrorPanel>
  131. </Fragment>
  132. );
  133. }
  134. const fields = Object.values([
  135. DurationFunctionField.P50,
  136. DurationFunctionField.P75,
  137. DurationFunctionField.P95,
  138. DurationFunctionField.P99,
  139. 'max',
  140. ]).map(v => `${v}(${TransactionMetric.TRANSACTION_DURATION})`);
  141. return (
  142. <Fragment>
  143. {header}
  144. <MetricsRequest
  145. {...requestCommonProps}
  146. query={new MutableSearch(query).formatString()} // TODO(metrics): not all tags will be compatible with metrics
  147. orgSlug={organization.slug}
  148. field={fields}
  149. >
  150. {durationRequestResponseProps => {
  151. const {errored, loading, reloading} = durationRequestResponseProps;
  152. const series = fields.map(field => {
  153. const {data} = transformMetricsToArea(
  154. {
  155. location,
  156. fields: [field],
  157. },
  158. durationRequestResponseProps
  159. );
  160. return data[0];
  161. });
  162. return (
  163. <Content
  164. series={series}
  165. errored={errored}
  166. loading={loading}
  167. reloading={reloading}
  168. {...contentCommonProps}
  169. />
  170. );
  171. }}
  172. </MetricsRequest>
  173. </Fragment>
  174. );
  175. }
  176. const yAxis = Object.values(DurationFunctionField).map(v => `${v}(${parameter})`);
  177. return (
  178. <Fragment>
  179. {header}
  180. <EventsRequest
  181. {...requestCommonProps}
  182. organization={organization}
  183. showLoading={false}
  184. includePrevious={false}
  185. yAxis={yAxis}
  186. partial
  187. withoutZerofill={withoutZerofill}
  188. referrer="api.performance.transaction-summary.duration-chart"
  189. >
  190. {({results, errored, loading, reloading, timeframe: timeFrame}) => (
  191. <Content
  192. series={results}
  193. errored={errored}
  194. loading={loading}
  195. reloading={reloading}
  196. timeFrame={timeFrame}
  197. {...contentCommonProps}
  198. />
  199. )}
  200. </EventsRequest>
  201. </Fragment>
  202. );
  203. }
  204. export default withRouter(DurationChart);