metricsBaselineContainer.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import {useEffect, useState} from 'react';
  2. import {InjectedRouter} from 'react-router';
  3. import {LineSeriesOption} from 'echarts';
  4. import {Location} from 'history';
  5. import {doEventsRequest} from 'sentry/actionCreators/events';
  6. import {Client} from 'sentry/api';
  7. import LineSeries from 'sentry/components/charts/series/lineSeries';
  8. import {isMultiSeriesStats} from 'sentry/components/charts/utils';
  9. import {EventsStats, Organization} from 'sentry/types';
  10. import {defined} from 'sentry/utils';
  11. import {getUtcToLocalDateObject} from 'sentry/utils/dates';
  12. import {EventsTableData} from 'sentry/utils/discover/discoverQuery';
  13. import EventView from 'sentry/utils/discover/eventView';
  14. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  15. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  16. import {useMetricsCardinalityContext} from 'sentry/utils/performance/contexts/metricsCardinality';
  17. import theme from 'sentry/utils/theme';
  18. import {transformSeries} from 'sentry/views/dashboardsV2/widgetCard/widgetQueries';
  19. import {SeriesWithOrdering} from '../dashboardsV2/datasetConfig/errorsAndTransactions';
  20. import ResultsChart from './resultsChart';
  21. import {usesTransactionsDataset} from './utils';
  22. type MetricsBaselineContainerProps = {
  23. api: Client;
  24. confirmedQuery: boolean;
  25. eventView: EventView;
  26. location: Location;
  27. onAxisChange: (value: string[]) => void;
  28. onDisplayChange: (value: string) => void;
  29. onIntervalChange: (value: string | undefined) => void;
  30. onTopEventsChange: (value: string) => void;
  31. organization: Organization;
  32. router: InjectedRouter;
  33. // chart footer props
  34. total: number | null;
  35. yAxis: string[];
  36. };
  37. export function MetricsBaselineContainer({
  38. eventView,
  39. yAxis,
  40. location,
  41. organization,
  42. api,
  43. router,
  44. ...props
  45. }: MetricsBaselineContainerProps) {
  46. const metricsCardinality = useMetricsCardinalityContext();
  47. const displayMode = eventView.getDisplayMode();
  48. const isRollingOut =
  49. organization.features.includes('server-side-sampling') &&
  50. organization.features.includes('mep-rollout-flag') &&
  51. organization.features.includes('discover-metrics-baseline');
  52. const disableProcessedBaselineToggle =
  53. metricsCardinality.outcome?.forceTransactionsOnly ||
  54. displayMode !== 'default' ||
  55. !usesTransactionsDataset(eventView, yAxis);
  56. const apiPayload = eventView.getEventsAPIPayload(location);
  57. apiPayload.query = '';
  58. const pageFilters = eventView.getPageFilters();
  59. const start = pageFilters.datetime.start
  60. ? getUtcToLocalDateObject(pageFilters.datetime.start)
  61. : null;
  62. const end = pageFilters.datetime.end
  63. ? getUtcToLocalDateObject(pageFilters.datetime.end)
  64. : null;
  65. const showBaseline = location.query.baseline === '0' ? false : true;
  66. const [metricsCompatible, setMetricsCompatible] = useState<boolean>(false);
  67. const [processedLineSeries, setProcessedLineSeries] = useState<
  68. LineSeriesOption[] | undefined
  69. >(undefined);
  70. const [processedTotal, setProcessedTotal] = useState<number | undefined>(undefined);
  71. const [loadingTotals, setLoadingTotals] = useState<boolean>(true);
  72. useEffect(() => {
  73. let shouldCancelRequest = false;
  74. if (!isRollingOut || disableProcessedBaselineToggle || !showBaseline) {
  75. setProcessedTotal(undefined);
  76. setLoadingTotals(false);
  77. return undefined;
  78. }
  79. doDiscoverQuery<EventsTableData>(api, `/organizations/${organization.slug}/events/`, {
  80. ...eventView.generateQueryStringObject(),
  81. field: ['count()'],
  82. query: '',
  83. sort: [],
  84. referrer: 'api.discover.processed-baseline-total',
  85. ...{dataset: DiscoverDatasets.METRICS},
  86. })
  87. .then(response => {
  88. if (shouldCancelRequest) {
  89. return;
  90. }
  91. const [data] = response;
  92. const total = data.data[0]?.['count()'];
  93. if (defined(total)) {
  94. setProcessedTotal(parseInt(total as string, 10));
  95. setLoadingTotals(false);
  96. } else {
  97. setProcessedTotal(undefined);
  98. setLoadingTotals(false);
  99. }
  100. })
  101. .catch(() => {
  102. if (shouldCancelRequest) {
  103. return;
  104. }
  105. setMetricsCompatible(false);
  106. setLoadingTotals(false);
  107. setProcessedTotal(undefined);
  108. });
  109. return () => {
  110. shouldCancelRequest = true;
  111. };
  112. }, [
  113. disableProcessedBaselineToggle,
  114. api,
  115. organization,
  116. eventView,
  117. showBaseline,
  118. isRollingOut,
  119. ]);
  120. useEffect(() => {
  121. let shouldCancelRequest = false;
  122. if (!isRollingOut || disableProcessedBaselineToggle || !showBaseline) {
  123. setProcessedLineSeries(undefined);
  124. return undefined;
  125. }
  126. doEventsRequest(api, {
  127. organization,
  128. partial: true,
  129. start,
  130. end,
  131. yAxis,
  132. environment: pageFilters.environments,
  133. period: pageFilters.datetime.period,
  134. interval: eventView.interval,
  135. project: pageFilters.projects,
  136. query: '',
  137. queryExtras: {dataset: DiscoverDatasets.METRICS},
  138. })
  139. .then(response => {
  140. if (shouldCancelRequest) {
  141. return;
  142. }
  143. const additionalSeries: LineSeriesOption[] = [];
  144. if (isMultiSeriesStats(response)) {
  145. let seriesWithOrdering: SeriesWithOrdering[] = [];
  146. seriesWithOrdering = Object.keys(response).map((seriesName: string) => {
  147. const prefixedName = `processed events: ${seriesName}`;
  148. const seriesData: EventsStats = response[seriesName];
  149. return [
  150. seriesData.order || 0,
  151. transformSeries(seriesData, prefixedName, seriesName),
  152. ];
  153. });
  154. const additionalSeriesColor = theme.charts.getColorPalette(
  155. seriesWithOrdering.length - 2
  156. );
  157. seriesWithOrdering.forEach(([order, series]) =>
  158. additionalSeries.push(
  159. LineSeries({
  160. name: series.seriesName,
  161. data: series.data.map(({name, value}) => [name, value]),
  162. lineStyle: {
  163. color: additionalSeriesColor[order],
  164. type: 'dashed',
  165. width: 1,
  166. opacity: 0.5,
  167. },
  168. itemStyle: {color: additionalSeriesColor[order]},
  169. animation: false,
  170. animationThreshold: 1,
  171. animationDuration: 0,
  172. })
  173. )
  174. );
  175. } else {
  176. const field = yAxis[0];
  177. const prefixedName = `processed events: ${field}`;
  178. const transformed = transformSeries(response, prefixedName, field);
  179. additionalSeries.push(
  180. LineSeries({
  181. name: transformed.seriesName,
  182. data: transformed.data.map(({name, value}) => [name, value]),
  183. lineStyle: {type: 'dashed', width: 1, opacity: 0.5},
  184. animation: false,
  185. animationThreshold: 1,
  186. animationDuration: 0,
  187. })
  188. );
  189. }
  190. setMetricsCompatible(true);
  191. setProcessedLineSeries(additionalSeries);
  192. })
  193. .catch(() => {
  194. if (shouldCancelRequest) {
  195. return;
  196. }
  197. setMetricsCompatible(false);
  198. });
  199. return () => {
  200. shouldCancelRequest = true;
  201. };
  202. }, [
  203. disableProcessedBaselineToggle,
  204. api,
  205. organization,
  206. start,
  207. end,
  208. yAxis,
  209. pageFilters.environments,
  210. pageFilters.datetime.period,
  211. pageFilters.projects,
  212. eventView.interval,
  213. showBaseline,
  214. isRollingOut,
  215. ]);
  216. return (
  217. <ResultsChart
  218. organization={organization}
  219. eventView={eventView}
  220. location={location}
  221. yAxis={yAxis}
  222. router={router}
  223. processedLineSeries={showBaseline ? processedLineSeries : undefined}
  224. disableProcessedBaselineToggle={
  225. disableProcessedBaselineToggle || !metricsCompatible
  226. }
  227. processedTotal={processedTotal}
  228. loadingProcessedTotals={loadingTotals}
  229. showBaseline={showBaseline}
  230. setShowBaseline={(value: boolean) => {
  231. router.push({
  232. pathname: location.pathname,
  233. query: {
  234. ...location.query,
  235. baseline: value === false ? '0' : '1',
  236. },
  237. });
  238. }}
  239. {...props}
  240. />
  241. );
  242. }