charts.tsx 10 KB

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