charts.tsx 9.5 KB

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