releaseEventsChart.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import {Fragment} from 'react';
  2. // eslint-disable-next-line no-restricted-imports
  3. import {withRouter, WithRouterProps} from 'react-router';
  4. import {useTheme} from '@emotion/react';
  5. import type {ToolboxComponentOption} from 'echarts';
  6. import {Client} from 'sentry/api';
  7. import EventsChart from 'sentry/components/charts/eventsChart';
  8. import EventsRequest from 'sentry/components/charts/eventsRequest';
  9. import {HeaderTitleLegend, HeaderValue} from 'sentry/components/charts/styles';
  10. import {getInterval} from 'sentry/components/charts/utils';
  11. import QuestionTooltip from 'sentry/components/questionTooltip';
  12. import {t} from 'sentry/locale';
  13. import {
  14. Organization,
  15. ReleaseComparisonChartType,
  16. ReleaseProject,
  17. ReleaseWithHealth,
  18. } from 'sentry/types';
  19. import {tooltipFormatter} from 'sentry/utils/discover/charts';
  20. import {aggregateOutputType} from 'sentry/utils/discover/fields';
  21. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  22. import useApi from 'sentry/utils/useApi';
  23. import withOrganization from 'sentry/utils/withOrganization';
  24. import {getTermHelp, PERFORMANCE_TERM} from 'sentry/views/performance/data';
  25. import {
  26. generateReleaseMarkLines,
  27. releaseComparisonChartTitles,
  28. releaseMarkLinesLabels,
  29. } from '../../utils';
  30. type Props = WithRouterProps & {
  31. chartType: ReleaseComparisonChartType;
  32. diff: React.ReactNode;
  33. organization: Organization;
  34. project: ReleaseProject;
  35. release: ReleaseWithHealth;
  36. value: React.ReactNode;
  37. end?: string;
  38. period?: string | null;
  39. start?: string;
  40. utc?: boolean;
  41. };
  42. function ReleaseEventsChart({
  43. release,
  44. project,
  45. chartType,
  46. value,
  47. diff,
  48. organization,
  49. router,
  50. period,
  51. start,
  52. end,
  53. utc,
  54. location,
  55. }: Props) {
  56. const api = useApi();
  57. const theme = useTheme();
  58. function getColors() {
  59. const colors = theme.charts.getColorPalette(14);
  60. switch (chartType) {
  61. case ReleaseComparisonChartType.ERROR_COUNT:
  62. return [colors[12]];
  63. case ReleaseComparisonChartType.TRANSACTION_COUNT:
  64. return [colors[0]];
  65. case ReleaseComparisonChartType.FAILURE_RATE:
  66. return [colors[9]];
  67. default:
  68. return undefined;
  69. }
  70. }
  71. function getQuery() {
  72. const releaseFilter = `release:${release.version}`;
  73. switch (chartType) {
  74. case ReleaseComparisonChartType.ERROR_COUNT:
  75. return new MutableSearch([
  76. '!event.type:transaction',
  77. releaseFilter,
  78. ]).formatString();
  79. case ReleaseComparisonChartType.TRANSACTION_COUNT:
  80. return new MutableSearch([
  81. 'event.type:transaction',
  82. releaseFilter,
  83. ]).formatString();
  84. case ReleaseComparisonChartType.FAILURE_RATE:
  85. return new MutableSearch([
  86. 'event.type:transaction',
  87. releaseFilter,
  88. ]).formatString();
  89. default:
  90. return '';
  91. }
  92. }
  93. function getField() {
  94. switch (chartType) {
  95. case ReleaseComparisonChartType.ERROR_COUNT:
  96. return ['count()'];
  97. case ReleaseComparisonChartType.TRANSACTION_COUNT:
  98. return ['count()'];
  99. case ReleaseComparisonChartType.FAILURE_RATE:
  100. return ['failure_rate()'];
  101. default:
  102. return undefined;
  103. }
  104. }
  105. function getYAxis() {
  106. switch (chartType) {
  107. case ReleaseComparisonChartType.ERROR_COUNT:
  108. return 'count()';
  109. case ReleaseComparisonChartType.TRANSACTION_COUNT:
  110. return 'count()';
  111. case ReleaseComparisonChartType.FAILURE_RATE:
  112. return 'failure_rate()';
  113. default:
  114. return '';
  115. }
  116. }
  117. function getHelp() {
  118. switch (chartType) {
  119. case ReleaseComparisonChartType.FAILURE_RATE:
  120. return getTermHelp(organization, PERFORMANCE_TERM.FAILURE_RATE);
  121. default:
  122. return null;
  123. }
  124. }
  125. const projects = location.query.project;
  126. const environments = location.query.environment;
  127. const markLines = generateReleaseMarkLines(release, project, theme, location);
  128. return (
  129. /**
  130. * EventsRequest is used to fetch the second series of Failure Rate chart.
  131. * First one is "This Release" - fetched as usual inside EventsChart
  132. * component and this one is "All Releases" that's shoehorned in place
  133. * of Previous Period via previousSeriesTransformer
  134. */
  135. <EventsRequest
  136. organization={organization}
  137. api={new Client()}
  138. period={period}
  139. project={projects}
  140. environment={environments}
  141. start={start}
  142. end={end}
  143. interval={getInterval({start, end, period, utc}, 'high')}
  144. query="event.type:transaction"
  145. includePrevious={false}
  146. currentSeriesNames={[t('All Releases')]}
  147. yAxis={getYAxis()}
  148. field={getField()}
  149. confirmedQuery={chartType === ReleaseComparisonChartType.FAILURE_RATE}
  150. partial
  151. referrer="api.releases.release-details-chart"
  152. >
  153. {({timeseriesData, loading, reloading}) => (
  154. <EventsChart
  155. query={getQuery()}
  156. yAxis={getYAxis()}
  157. field={getField()}
  158. colors={getColors()}
  159. api={api}
  160. router={router}
  161. organization={organization}
  162. disableReleases
  163. disablePrevious
  164. showLegend
  165. projects={projects}
  166. environments={environments}
  167. start={start ?? null}
  168. end={end ?? null}
  169. period={period}
  170. utc={utc}
  171. currentSeriesName={t('This Release') + (loading || reloading ? ' ' : '')} // HACK: trigger echarts rerender without remounting
  172. previousSeriesName={t('All Releases')}
  173. disableableSeries={[t('This Release'), t('All Releases')]}
  174. chartHeader={
  175. <Fragment>
  176. <HeaderTitleLegend>
  177. {releaseComparisonChartTitles[chartType]}
  178. {getHelp() && (
  179. <QuestionTooltip size="sm" position="top" title={getHelp()} />
  180. )}
  181. </HeaderTitleLegend>
  182. <HeaderValue>
  183. {value} {diff}
  184. </HeaderValue>
  185. </Fragment>
  186. }
  187. legendOptions={{
  188. right: 10,
  189. top: 0,
  190. textStyle: {
  191. padding: [2, 0, 0, 0],
  192. },
  193. }}
  194. chartOptions={{
  195. grid: {left: '10px', right: '10px', top: '70px', bottom: '0px'},
  196. tooltip: {
  197. trigger: 'axis',
  198. truncate: 80,
  199. valueFormatter: (val: number, label?: string) => {
  200. if (label && Object.values(releaseMarkLinesLabels).includes(label)) {
  201. return '';
  202. }
  203. return tooltipFormatter(val, aggregateOutputType(getYAxis()));
  204. },
  205. } as ToolboxComponentOption,
  206. }}
  207. usePageZoom
  208. height={240}
  209. seriesTransformer={series => [...series, ...markLines]}
  210. previousSeriesTransformer={series => {
  211. if (chartType === ReleaseComparisonChartType.FAILURE_RATE) {
  212. return timeseriesData?.[0];
  213. }
  214. return series;
  215. }}
  216. referrer="api.releases.release-details-chart"
  217. />
  218. )}
  219. </EventsRequest>
  220. );
  221. }
  222. export default withOrganization(withRouter(ReleaseEventsChart));