content.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import {Location} from 'history';
  2. import isEqual from 'lodash/isEqual';
  3. import pick from 'lodash/pick';
  4. import AsyncComponent from 'sentry/components/asyncComponent';
  5. import ErrorPanel from 'sentry/components/charts/errorPanel';
  6. import LoadingPanel from 'sentry/components/charts/loadingPanel';
  7. import {IconWarning} from 'sentry/icons';
  8. import {OrganizationSummary} from 'sentry/types';
  9. import {defined} from 'sentry/utils';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import {Theme} from 'sentry/utils/theme';
  12. import {ViewProps} from '../../../types';
  13. import {QUERY_KEYS} from '../../../utils';
  14. import {filterToColor, SpanOperationBreakdownFilter} from '../../filter';
  15. import Chart from './chart';
  16. import {transformData} from './utils';
  17. type ApiResult = Record<string, number>;
  18. type Props = AsyncComponent['props'] &
  19. ViewProps & {
  20. currentFilter: SpanOperationBreakdownFilter;
  21. fields: string[];
  22. location: Location;
  23. organization: OrganizationSummary;
  24. };
  25. type State = AsyncComponent['state'] & {
  26. chartData: {data: ApiResult[]} | null;
  27. };
  28. /**
  29. * Fetch and render a bar chart that shows event volume
  30. * for each duration bucket. We always render 15 buckets of
  31. * equal widths based on the endpoints min + max durations.
  32. *
  33. * This graph visualizes how many transactions were recorded
  34. * at each duration bucket, showing the modality of the transaction.
  35. */
  36. class Content extends AsyncComponent<Props, State> {
  37. getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
  38. const {
  39. organization,
  40. query,
  41. start,
  42. end,
  43. statsPeriod,
  44. environment,
  45. project,
  46. fields,
  47. location,
  48. } = this.props;
  49. const eventView = EventView.fromSavedQuery({
  50. id: '',
  51. name: '',
  52. version: 2,
  53. fields,
  54. orderby: '',
  55. projects: project,
  56. range: statsPeriod,
  57. query,
  58. environment,
  59. start,
  60. end,
  61. });
  62. const apiPayload = eventView.getEventsAPIPayload(location);
  63. apiPayload.referrer = 'api.performance.durationpercentilechart';
  64. const endpoint = organization.features.includes(
  65. 'performance-frontend-use-events-endpoint'
  66. )
  67. ? `/organizations/${organization.slug}/events/`
  68. : `/organizations/${organization.slug}/eventsv2/`;
  69. return [['chartData', endpoint, {query: apiPayload}]];
  70. }
  71. componentDidUpdate(prevProps: Props) {
  72. if (this.shouldRefetchData(prevProps)) {
  73. this.fetchData();
  74. }
  75. }
  76. shouldRefetchData(prevProps: Props) {
  77. if (this.state.loading) {
  78. return false;
  79. }
  80. return !isEqual(pick(prevProps, QUERY_KEYS), pick(this.props, QUERY_KEYS));
  81. }
  82. renderLoading() {
  83. return <LoadingPanel data-test-id="histogram-loading" />;
  84. }
  85. renderError() {
  86. // Don't call super as we don't really need issues for this.
  87. return (
  88. <ErrorPanel>
  89. <IconWarning color="gray300" size="lg" />
  90. </ErrorPanel>
  91. );
  92. }
  93. renderBody() {
  94. const {currentFilter, organization} = this.props;
  95. const {chartData} = this.state;
  96. if (!defined(chartData)) {
  97. return null;
  98. }
  99. const colors = (theme: Theme) =>
  100. currentFilter === SpanOperationBreakdownFilter.None
  101. ? theme.charts.getColorPalette(1)
  102. : [filterToColor(currentFilter)];
  103. return (
  104. <Chart
  105. series={transformData(
  106. chartData.data,
  107. !organization.features.includes('performance-frontend-use-events-endpoint')
  108. )}
  109. colors={colors}
  110. />
  111. );
  112. }
  113. }
  114. export default Content;