charts.tsx 10 KB

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