releaseEventsChart.tsx 7.0 KB

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