content.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import {Theme} from '@emotion/react';
  2. import {Location} from 'history';
  3. import isEqual from 'lodash/isEqual';
  4. import pick from 'lodash/pick';
  5. import AsyncComponent from 'sentry/components/asyncComponent';
  6. import ErrorPanel from 'sentry/components/charts/errorPanel';
  7. import LoadingPanel from 'sentry/components/charts/loadingPanel';
  8. import {IconWarning} from 'sentry/icons';
  9. import {OrganizationSummary} from 'sentry/types';
  10. import {defined} from 'sentry/utils';
  11. import EventView from 'sentry/utils/discover/eventView';
  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 = `/organizations/${organization.slug}/events/`;
  65. return [['chartData', endpoint, {query: apiPayload}]];
  66. }
  67. componentDidUpdate(prevProps: Props) {
  68. if (this.shouldRefetchData(prevProps)) {
  69. this.fetchData();
  70. }
  71. }
  72. shouldRefetchData(prevProps: Props) {
  73. if (this.state.loading) {
  74. return false;
  75. }
  76. return !isEqual(pick(prevProps, QUERY_KEYS), pick(this.props, QUERY_KEYS));
  77. }
  78. renderLoading() {
  79. return <LoadingPanel data-test-id="histogram-loading" />;
  80. }
  81. renderError() {
  82. // Don't call super as we don't really need issues for this.
  83. return (
  84. <ErrorPanel>
  85. <IconWarning color="gray300" size="lg" />
  86. </ErrorPanel>
  87. );
  88. }
  89. renderBody() {
  90. const {currentFilter} = this.props;
  91. const {chartData} = this.state;
  92. if (!defined(chartData)) {
  93. return null;
  94. }
  95. const colors = (theme: Theme) =>
  96. currentFilter === SpanOperationBreakdownFilter.None
  97. ? theme.charts.getColorPalette(1)
  98. : [filterToColor(currentFilter)];
  99. return <Chart series={transformData(chartData.data, false)} colors={colors} />;
  100. }
  101. }
  102. export default Content;