metricsBaselineContainer.tsx 8.5 KB

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