charts.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import {browserHistory} from 'react-router';
  2. import {Location} from 'history';
  3. import OptionSelector from 'sentry/components/charts/optionSelector';
  4. import {
  5. ChartContainer,
  6. ChartControls,
  7. InlineContainer,
  8. SectionHeading,
  9. SectionValue,
  10. } from 'sentry/components/charts/styles';
  11. import {Panel} from 'sentry/components/panels';
  12. import Placeholder from 'sentry/components/placeholder';
  13. import {t} from 'sentry/locale';
  14. import {Organization, SelectValue} from 'sentry/types';
  15. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  16. import EventView from 'sentry/utils/discover/eventView';
  17. import {formatPercentage} from 'sentry/utils/formatters';
  18. import {useMEPSettingContext} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  19. import {removeHistogramQueryStrings} from 'sentry/utils/performance/histogram';
  20. import {decodeScalar} from 'sentry/utils/queryString';
  21. import {
  22. canUseMetricsInTransactionSummary,
  23. getTransactionMEPParamsIfApplicable,
  24. } from 'sentry/views/performance/transactionSummary/transactionOverview/utils';
  25. import {DisplayModes} from 'sentry/views/performance/transactionSummary/utils';
  26. import {TransactionsListOption} from 'sentry/views/releases/detail/overview';
  27. import {TrendColumnField, TrendFunctionField} from '../../trends/types';
  28. import {TRENDS_FUNCTIONS, TRENDS_PARAMETERS} from '../../trends/utils';
  29. import {SpanOperationBreakdownFilter} from '../filter';
  30. import LatencyChartControls from './latencyChart/chartControls';
  31. import {ZOOM_END, ZOOM_START} from './latencyChart/utils';
  32. import DurationChart from './durationChart';
  33. import DurationPercentileChart from './durationPercentileChart';
  34. import LatencyChart from './latencyChart';
  35. import TrendChart from './trendChart';
  36. import UserMiseryChart from './userMiseryChart';
  37. import VitalsChart from './vitalsChart';
  38. function generateDisplayOptions(
  39. currentFilter: SpanOperationBreakdownFilter
  40. ): SelectValue<string>[] {
  41. if (currentFilter === SpanOperationBreakdownFilter.None) {
  42. return [
  43. {value: DisplayModes.DURATION, label: t('Duration Breakdown')},
  44. {value: DisplayModes.DURATION_PERCENTILE, label: t('Duration Percentiles')},
  45. {value: DisplayModes.LATENCY, label: t('Duration Distribution')},
  46. {value: DisplayModes.TREND, label: t('Trends')},
  47. {value: DisplayModes.VITALS, label: t('Web Vitals')},
  48. {value: DisplayModes.USER_MISERY, label: t('User Misery')},
  49. ];
  50. }
  51. // A span operation name breakdown has been chosen.
  52. return [
  53. {value: DisplayModes.DURATION, label: t('Span Operation Breakdown')},
  54. {value: DisplayModes.DURATION_PERCENTILE, label: t('Span Operation Percentiles')},
  55. {value: DisplayModes.LATENCY, label: t('Span Operation Distribution')},
  56. {value: DisplayModes.TREND, label: t('Trends')},
  57. {value: DisplayModes.VITALS, label: t('Web Vitals')},
  58. ];
  59. }
  60. const TREND_FUNCTIONS_OPTIONS: SelectValue<string>[] = TRENDS_FUNCTIONS.map(
  61. ({field, label}) => ({
  62. value: field,
  63. label,
  64. })
  65. );
  66. type Props = {
  67. currentFilter: SpanOperationBreakdownFilter;
  68. eventView: EventView;
  69. location: Location;
  70. organization: Organization;
  71. totalValue: number | null;
  72. withoutZerofill: boolean;
  73. unfilteredTotalValue?: number | null;
  74. };
  75. function TransactionSummaryCharts({
  76. totalValue,
  77. eventView,
  78. organization,
  79. location,
  80. currentFilter,
  81. withoutZerofill,
  82. unfilteredTotalValue,
  83. }: Props) {
  84. function handleDisplayChange(value: string) {
  85. const display = decodeScalar(location.query.display, DisplayModes.DURATION);
  86. trackAdvancedAnalyticsEvent(
  87. 'performance_views.transaction_summary.change_chart_display',
  88. {
  89. organization,
  90. from_chart: display,
  91. to_chart: value,
  92. }
  93. );
  94. browserHistory.push({
  95. pathname: location.pathname,
  96. query: {
  97. ...removeHistogramQueryStrings(location, [ZOOM_START, ZOOM_END]),
  98. display: value,
  99. },
  100. });
  101. }
  102. function handleTrendDisplayChange(value: string) {
  103. browserHistory.push({
  104. pathname: location.pathname,
  105. query: {...location.query, trendFunction: value},
  106. });
  107. }
  108. function handleTrendColumnChange(value: string) {
  109. browserHistory.push({
  110. pathname: location.pathname,
  111. query: {...location.query, trendColumn: value},
  112. });
  113. }
  114. const TREND_PARAMETERS_OPTIONS: SelectValue<string>[] = TRENDS_PARAMETERS.map(
  115. ({column, label}) => ({
  116. value: column,
  117. label,
  118. })
  119. );
  120. let display = decodeScalar(location.query.display, DisplayModes.DURATION);
  121. let trendFunction = decodeScalar(
  122. location.query.trendFunction,
  123. TREND_FUNCTIONS_OPTIONS[0].value
  124. ) as TrendFunctionField;
  125. let trendColumn = decodeScalar(
  126. location.query.trendColumn,
  127. TREND_PARAMETERS_OPTIONS[0].value
  128. );
  129. if (!Object.values(DisplayModes).includes(display as DisplayModes)) {
  130. display = DisplayModes.DURATION;
  131. }
  132. if (!Object.values(TrendFunctionField).includes(trendFunction)) {
  133. trendFunction = TrendFunctionField.P50;
  134. }
  135. if (!Object.values(TrendColumnField).includes(trendColumn as TrendColumnField)) {
  136. trendColumn = TrendColumnField.DURATION;
  137. }
  138. const releaseQueryExtra = {
  139. yAxis: display === DisplayModes.VITALS ? 'countVital' : 'countDuration',
  140. showTransactions:
  141. display === DisplayModes.VITALS
  142. ? TransactionsListOption.SLOW_LCP
  143. : display === DisplayModes.DURATION
  144. ? TransactionsListOption.SLOW
  145. : undefined,
  146. };
  147. const mepSetting = useMEPSettingContext();
  148. const queryExtras = getTransactionMEPParamsIfApplicable(
  149. mepSetting,
  150. organization,
  151. location
  152. );
  153. // For mep-incompatible displays hide event count
  154. const hideTransactionCount =
  155. canUseMetricsInTransactionSummary(organization) && display === DisplayModes.TREND;
  156. // For partially-mep-compatible displays show event count as a %
  157. const showTransactionCountAsPercentage =
  158. canUseMetricsInTransactionSummary(organization) && display === DisplayModes.LATENCY;
  159. function getTotalValue() {
  160. if (totalValue === null || hideTransactionCount) {
  161. return <Placeholder height="24px" />;
  162. }
  163. if (showTransactionCountAsPercentage && unfilteredTotalValue) {
  164. return formatPercentage(totalValue / unfilteredTotalValue);
  165. }
  166. return totalValue.toLocaleString();
  167. }
  168. return (
  169. <Panel>
  170. <ChartContainer data-test-id="transaction-summary-charts">
  171. {display === DisplayModes.LATENCY && (
  172. <LatencyChart
  173. organization={organization}
  174. location={location}
  175. query={eventView.query}
  176. project={eventView.project}
  177. environment={eventView.environment}
  178. start={eventView.start}
  179. end={eventView.end}
  180. statsPeriod={eventView.statsPeriod}
  181. currentFilter={currentFilter}
  182. queryExtras={queryExtras}
  183. totalCount={showTransactionCountAsPercentage ? totalValue : null}
  184. />
  185. )}
  186. {display === DisplayModes.DURATION && (
  187. <DurationChart
  188. organization={organization}
  189. query={eventView.query}
  190. queryExtra={releaseQueryExtra}
  191. project={eventView.project}
  192. environment={eventView.environment}
  193. start={eventView.start}
  194. end={eventView.end}
  195. statsPeriod={eventView.statsPeriod}
  196. currentFilter={currentFilter}
  197. withoutZerofill={withoutZerofill}
  198. queryExtras={queryExtras}
  199. />
  200. )}
  201. {display === DisplayModes.DURATION_PERCENTILE && (
  202. <DurationPercentileChart
  203. organization={organization}
  204. location={location}
  205. query={eventView.query}
  206. project={eventView.project}
  207. environment={eventView.environment}
  208. start={eventView.start}
  209. end={eventView.end}
  210. statsPeriod={eventView.statsPeriod}
  211. currentFilter={currentFilter}
  212. queryExtras={queryExtras}
  213. />
  214. )}
  215. {display === DisplayModes.TREND && (
  216. <TrendChart
  217. trendFunction={trendFunction}
  218. trendParameter={trendColumn}
  219. organization={organization}
  220. query={eventView.query}
  221. queryExtra={releaseQueryExtra}
  222. project={eventView.project}
  223. environment={eventView.environment}
  224. start={eventView.start}
  225. end={eventView.end}
  226. statsPeriod={eventView.statsPeriod}
  227. withoutZerofill={withoutZerofill}
  228. />
  229. )}
  230. {display === DisplayModes.VITALS && (
  231. <VitalsChart
  232. organization={organization}
  233. query={eventView.query}
  234. queryExtra={releaseQueryExtra}
  235. project={eventView.project}
  236. environment={eventView.environment}
  237. start={eventView.start}
  238. end={eventView.end}
  239. statsPeriod={eventView.statsPeriod}
  240. withoutZerofill={withoutZerofill}
  241. queryExtras={queryExtras}
  242. />
  243. )}
  244. {display === DisplayModes.USER_MISERY && (
  245. <UserMiseryChart
  246. organization={organization}
  247. query={eventView.query}
  248. queryExtra={releaseQueryExtra}
  249. project={eventView.project}
  250. environment={eventView.environment}
  251. start={eventView.start}
  252. end={eventView.end}
  253. statsPeriod={eventView.statsPeriod}
  254. withoutZerofill={withoutZerofill}
  255. />
  256. )}
  257. </ChartContainer>
  258. <ChartControls>
  259. <InlineContainer>
  260. <SectionHeading key="total-heading">
  261. {hideTransactionCount ? '' : t('Total Transactions')}
  262. </SectionHeading>
  263. <SectionValue key="total-value">{getTotalValue()}</SectionValue>
  264. </InlineContainer>
  265. <InlineContainer>
  266. {display === DisplayModes.TREND && (
  267. <OptionSelector
  268. title={t('Percentile')}
  269. selected={trendFunction}
  270. options={TREND_FUNCTIONS_OPTIONS}
  271. onChange={handleTrendDisplayChange}
  272. />
  273. )}
  274. {display === DisplayModes.TREND && (
  275. <OptionSelector
  276. title={t('Parameter')}
  277. selected={trendColumn}
  278. options={TREND_PARAMETERS_OPTIONS}
  279. onChange={handleTrendColumnChange}
  280. />
  281. )}
  282. {display === DisplayModes.LATENCY && (
  283. <LatencyChartControls location={location} />
  284. )}
  285. <OptionSelector
  286. title={t('Display')}
  287. selected={display}
  288. options={generateDisplayOptions(currentFilter)}
  289. onChange={handleDisplayChange}
  290. />
  291. </InlineContainer>
  292. </ChartControls>
  293. </Panel>
  294. );
  295. }
  296. export default TransactionSummaryCharts;