content.tsx 3.3 KB

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