releaseEventsChart.tsx 7.3 KB

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