screenCharts.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import {Fragment, useEffect, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import Alert from 'sentry/components/alert';
  5. import _EventsRequest from 'sentry/components/charts/eventsRequest';
  6. import {getInterval} from 'sentry/components/charts/utils';
  7. import LoadingContainer from 'sentry/components/loading/loadingContainer';
  8. import {CHART_PALETTE} from 'sentry/constants/chartPalette';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Series, SeriesDataUnit} from 'sentry/types/echarts';
  12. import {defined} from 'sentry/utils';
  13. import {tooltipFormatterUsingAggregateOutputType} from 'sentry/utils/discover/charts';
  14. import EventView from 'sentry/utils/discover/eventView';
  15. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  16. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  17. import {useLocation} from 'sentry/utils/useLocation';
  18. import usePageFilters from 'sentry/utils/usePageFilters';
  19. import {formatVersion} from 'sentry/utils/versions/formatVersion';
  20. import Chart, {ChartType} from 'sentry/views/insights/common/components/chart';
  21. import MiniChartPanel from 'sentry/views/insights/common/components/miniChartPanel';
  22. import {useReleaseSelection} from 'sentry/views/insights/common/queries/useReleases';
  23. import {formatVersionAndCenterTruncate} from 'sentry/views/insights/common/utils/centerTruncate';
  24. import {STARFISH_CHART_INTERVAL_FIDELITY} from 'sentry/views/insights/common/utils/constants';
  25. import {appendReleaseFilters} from 'sentry/views/insights/common/utils/releaseComparison';
  26. import {useEventsStatsQuery} from 'sentry/views/insights/common/utils/useEventsStatsQuery';
  27. import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject';
  28. import {ScreensBarChart} from 'sentry/views/insights/mobile/screenload/components/charts/screenBarChart';
  29. import {useTableQuery} from 'sentry/views/insights/mobile/screenload/components/tables/screensTable';
  30. import {
  31. CHART_TITLES,
  32. OUTPUT_TYPE,
  33. YAXIS_COLUMNS,
  34. } from 'sentry/views/insights/mobile/screenload/constants';
  35. import {transformDeviceClassEvents} from 'sentry/views/insights/mobile/screenload/utils';
  36. export enum YAxis {
  37. WARM_START = 0,
  38. COLD_START = 1,
  39. TTID = 2,
  40. TTFD = 3,
  41. SLOW_FRAME_RATE = 4,
  42. FROZEN_FRAME_RATE = 5,
  43. THROUGHPUT = 6,
  44. COUNT = 7,
  45. }
  46. type Props = {
  47. yAxes: YAxis[];
  48. additionalFilters?: string[];
  49. chartHeight?: number;
  50. };
  51. export function ScreenCharts({yAxes, additionalFilters}: Props) {
  52. const pageFilter = usePageFilters();
  53. const location = useLocation();
  54. const {isProjectCrossPlatform, selectedPlatform: platform} = useCrossPlatformProject();
  55. const yAxisCols = yAxes.map(val => YAXIS_COLUMNS[val]);
  56. const {
  57. primaryRelease,
  58. secondaryRelease,
  59. isLoading: isReleasesLoading,
  60. } = useReleaseSelection();
  61. const queryString = useMemo(() => {
  62. const query = new MutableSearch([
  63. 'event.type:transaction',
  64. 'transaction.op:ui.load',
  65. ...(additionalFilters ?? []),
  66. ]);
  67. if (isProjectCrossPlatform) {
  68. query.addFilterValue('os.name', platform);
  69. }
  70. return appendReleaseFilters(query, primaryRelease, secondaryRelease);
  71. }, [
  72. additionalFilters,
  73. isProjectCrossPlatform,
  74. platform,
  75. primaryRelease,
  76. secondaryRelease,
  77. ]);
  78. const {
  79. data: series,
  80. isPending: isSeriesLoading,
  81. error: seriesError,
  82. } = useEventsStatsQuery({
  83. eventView: EventView.fromNewQueryWithPageFilters(
  84. {
  85. name: '',
  86. fields: ['release', ...yAxisCols],
  87. topEvents: '2',
  88. yAxis: [...yAxisCols],
  89. query: queryString,
  90. dataset: DiscoverDatasets.METRICS,
  91. version: 2,
  92. interval: getInterval(
  93. pageFilter.selection.datetime,
  94. STARFISH_CHART_INTERVAL_FIDELITY
  95. ),
  96. },
  97. pageFilter.selection
  98. ),
  99. enabled: !isReleasesLoading,
  100. // TODO: Change referrer
  101. referrer: 'api.starfish.mobile-screen-series',
  102. initialData: {},
  103. });
  104. useEffect(() => {
  105. if (defined(primaryRelease) || isReleasesLoading) {
  106. return;
  107. }
  108. Sentry.captureException(new Error('Screen summary missing releases'));
  109. }, [primaryRelease, isReleasesLoading]);
  110. const transformedReleaseSeries: {
  111. [yAxisName: string]: {
  112. [releaseVersion: string]: Series;
  113. };
  114. } = {};
  115. yAxes.forEach(val => {
  116. transformedReleaseSeries[YAXIS_COLUMNS[val]] = {};
  117. });
  118. if (defined(series)) {
  119. Object.keys(series).forEach(release => {
  120. const isPrimary = release === primaryRelease;
  121. Object.keys(series[release]).forEach(yAxis => {
  122. const label = release;
  123. if (yAxis in transformedReleaseSeries) {
  124. const data =
  125. series[release][yAxis]?.data.map(datum => {
  126. return {
  127. name: datum[0] * 1000,
  128. value: datum[1][0].count,
  129. } as SeriesDataUnit;
  130. }) ?? [];
  131. const color = isPrimary ? CHART_PALETTE[3][0] : CHART_PALETTE[3][1];
  132. transformedReleaseSeries[yAxis][release] = {
  133. seriesName: formatVersion(label, true),
  134. color,
  135. data,
  136. };
  137. }
  138. });
  139. });
  140. }
  141. const {data: deviceClassEvents, isPending: isDeviceClassEventsLoading} = useTableQuery({
  142. eventView: EventView.fromNewQueryWithLocation(
  143. {
  144. name: '',
  145. fields: ['device.class', 'release', ...yAxisCols],
  146. orderby: yAxisCols[0],
  147. yAxis: yAxisCols,
  148. query: queryString,
  149. dataset: DiscoverDatasets.METRICS,
  150. version: 2,
  151. },
  152. location
  153. ),
  154. enabled: !isReleasesLoading,
  155. referrer: 'api.starfish.mobile-device-breakdown',
  156. });
  157. if (isReleasesLoading) {
  158. return <LoadingContainer />;
  159. }
  160. if (!defined(primaryRelease) && !isReleasesLoading) {
  161. return (
  162. <Alert type="warning" showIcon>
  163. {t('Invalid selection. Try a different release or date range.')}
  164. </Alert>
  165. );
  166. }
  167. const transformedEvents = transformDeviceClassEvents({
  168. yAxes,
  169. primaryRelease,
  170. secondaryRelease,
  171. data: deviceClassEvents,
  172. });
  173. function renderCharts() {
  174. return (
  175. <Fragment>
  176. <Container>
  177. <div>
  178. <StyledRow>
  179. <ChartsContainerItem key="deviceClass">
  180. <ScreensBarChart
  181. chartOptions={[
  182. {
  183. title: t('TTID by Device Class'),
  184. yAxis: YAXIS_COLUMNS[yAxes[0]],
  185. series: Object.values(transformedEvents[YAXIS_COLUMNS[yAxes[0]]]),
  186. xAxisLabel: ['high', 'medium', 'low', 'Unknown'],
  187. subtitle: primaryRelease
  188. ? t(
  189. '%s v. %s',
  190. formatVersionAndCenterTruncate(primaryRelease, 12),
  191. secondaryRelease
  192. ? formatVersionAndCenterTruncate(secondaryRelease, 12)
  193. : ''
  194. )
  195. : '',
  196. },
  197. ]}
  198. chartKey="spansChart"
  199. chartHeight={80}
  200. isLoading={isDeviceClassEventsLoading}
  201. />
  202. </ChartsContainerItem>
  203. <ChartsContainerItem key="xyz">
  204. <MiniChartPanel
  205. title={t('Average TTID')}
  206. subtitle={
  207. primaryRelease
  208. ? t(
  209. '%s v. %s',
  210. formatVersionAndCenterTruncate(primaryRelease, 12),
  211. secondaryRelease
  212. ? formatVersionAndCenterTruncate(secondaryRelease, 12)
  213. : ''
  214. )
  215. : ''
  216. }
  217. >
  218. <Chart
  219. height={80}
  220. data={Object.values(
  221. transformedReleaseSeries[YAXIS_COLUMNS[yAxes[0]]]
  222. )}
  223. loading={isSeriesLoading}
  224. grid={{
  225. left: '0',
  226. right: '0',
  227. top: '8px',
  228. bottom: '0',
  229. }}
  230. showLegend
  231. definedAxisTicks={2}
  232. type={ChartType.LINE}
  233. aggregateOutputFormat={OUTPUT_TYPE[YAxis.TTID]}
  234. tooltipFormatterOptions={{
  235. valueFormatter: value =>
  236. tooltipFormatterUsingAggregateOutputType(
  237. value,
  238. OUTPUT_TYPE[YAxis.TTID]
  239. ),
  240. }}
  241. error={seriesError}
  242. />
  243. </MiniChartPanel>
  244. </ChartsContainerItem>
  245. </StyledRow>
  246. <StyledRow>
  247. <ChartsContainerItem key="deviceClass">
  248. <ScreensBarChart
  249. chartOptions={[
  250. {
  251. title: t('TTFD by Device Class'),
  252. yAxis: YAXIS_COLUMNS[yAxes[1]],
  253. series: Object.values(transformedEvents[YAXIS_COLUMNS[yAxes[1]]]),
  254. xAxisLabel: ['high', 'medium', 'low', 'Unknown'],
  255. subtitle: primaryRelease
  256. ? t(
  257. '%s v. %s',
  258. formatVersionAndCenterTruncate(primaryRelease, 12),
  259. secondaryRelease
  260. ? formatVersionAndCenterTruncate(secondaryRelease, 12)
  261. : ''
  262. )
  263. : '',
  264. },
  265. ]}
  266. chartKey="spansChart"
  267. chartHeight={80}
  268. isLoading={isDeviceClassEventsLoading}
  269. />
  270. </ChartsContainerItem>
  271. <ChartsContainerItem key="xyz">
  272. <MiniChartPanel
  273. title={t('Average TTFD')}
  274. subtitle={
  275. primaryRelease
  276. ? t(
  277. '%s v. %s',
  278. formatVersionAndCenterTruncate(primaryRelease, 12),
  279. secondaryRelease
  280. ? formatVersionAndCenterTruncate(secondaryRelease, 12)
  281. : ''
  282. )
  283. : ''
  284. }
  285. >
  286. <Chart
  287. height={80}
  288. data={Object.values(
  289. transformedReleaseSeries[YAXIS_COLUMNS[yAxes[1]]]
  290. )}
  291. loading={isSeriesLoading}
  292. grid={{
  293. left: '0',
  294. right: '0',
  295. top: '8px',
  296. bottom: '0',
  297. }}
  298. showLegend
  299. definedAxisTicks={2}
  300. type={ChartType.LINE}
  301. aggregateOutputFormat={OUTPUT_TYPE[YAxis.TTFD]}
  302. tooltipFormatterOptions={{
  303. valueFormatter: value =>
  304. tooltipFormatterUsingAggregateOutputType(
  305. value,
  306. OUTPUT_TYPE[YAxis.TTFD]
  307. ),
  308. }}
  309. error={seriesError}
  310. />
  311. </MiniChartPanel>
  312. </ChartsContainerItem>
  313. </StyledRow>
  314. </div>
  315. <ChartsContainerItem key="xyz">
  316. <MiniChartPanel
  317. title={CHART_TITLES[YAxis.COUNT]}
  318. subtitle={
  319. primaryRelease
  320. ? t(
  321. '%s v. %s',
  322. formatVersionAndCenterTruncate(primaryRelease, 12),
  323. secondaryRelease
  324. ? formatVersionAndCenterTruncate(secondaryRelease, 12)
  325. : ''
  326. )
  327. : ''
  328. }
  329. >
  330. <Chart
  331. data={Object.values(transformedReleaseSeries[YAXIS_COLUMNS[yAxes[2]]])}
  332. height={245}
  333. loading={isSeriesLoading}
  334. grid={{
  335. left: '0',
  336. right: '0',
  337. top: '8px',
  338. bottom: '0',
  339. }}
  340. showLegend
  341. definedAxisTicks={2}
  342. type={ChartType.LINE}
  343. aggregateOutputFormat={OUTPUT_TYPE[YAxis.COUNT]}
  344. tooltipFormatterOptions={{
  345. valueFormatter: value =>
  346. tooltipFormatterUsingAggregateOutputType(
  347. value,
  348. OUTPUT_TYPE[YAxis.COUNT]
  349. ),
  350. }}
  351. error={seriesError}
  352. />
  353. </MiniChartPanel>
  354. </ChartsContainerItem>
  355. </Container>
  356. </Fragment>
  357. );
  358. }
  359. return <div data-test-id="starfish-mobile-view">{renderCharts()}</div>;
  360. }
  361. const StyledRow = styled('div')`
  362. display: grid;
  363. grid-template-columns: repeat(2, 1fr);
  364. grid-column-gap: ${space(2)};
  365. `;
  366. const ChartsContainerItem = styled('div')`
  367. flex: 1;
  368. `;
  369. export const Spacer = styled('div')`
  370. margin-top: ${space(3)};
  371. `;
  372. const Container = styled('div')`
  373. display: grid;
  374. grid-template-columns: 2fr 1fr;
  375. grid-column-gap: ${space(2)};
  376. `;