index.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import {useTheme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import Alert from 'sentry/components/alert';
  4. import SearchBar from 'sentry/components/performance/searchBar';
  5. import {t} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import type {NewQuery} from 'sentry/types';
  8. import {defined} from 'sentry/utils';
  9. import EventView from 'sentry/utils/discover/eventView';
  10. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  11. import {decodeScalar} from 'sentry/utils/queryString';
  12. import {escapeFilterValue, MutableSearch} from 'sentry/utils/tokenizeSearch';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. import usePageFilters from 'sentry/utils/usePageFilters';
  16. import useRouter from 'sentry/utils/useRouter';
  17. import {prepareQueryForLandingPage} from 'sentry/views/performance/data';
  18. import {TOP_SCREENS} from 'sentry/views/performance/mobile/constants';
  19. import {
  20. getFreeTextFromQuery,
  21. YAxis,
  22. YAXIS_COLUMNS,
  23. } from 'sentry/views/performance/mobile/screenload/screens';
  24. import {useTableQuery} from 'sentry/views/performance/mobile/screenload/screens/screensTable';
  25. import {transformReleaseEvents} from 'sentry/views/performance/mobile/screenload/screens/utils';
  26. import {Referrer} from 'sentry/views/performance/mobile/ui/referrers';
  27. import {UIScreensTable} from 'sentry/views/performance/mobile/ui/screens/table';
  28. import {TopScreensChart} from 'sentry/views/performance/mobile/ui/screens/topScreensChart';
  29. import {getTransactionSearchQuery} from 'sentry/views/performance/utils';
  30. import {useReleaseSelection} from 'sentry/views/starfish/queries/useReleases';
  31. import {SpanMetricsField} from 'sentry/views/starfish/types';
  32. import {appendReleaseFilters} from 'sentry/views/starfish/utils/releaseComparison';
  33. const Y_AXES = [YAxis.SLOW_FRAMES, YAxis.FROZEN_FRAMES, YAxis.FRAMES_DELAY];
  34. const Y_AXIS_COLUMNS = [
  35. 'avg(mobile.slow_frames)',
  36. 'avg(mobile.frozen_frames)',
  37. 'avg(mobile.frames_delay)',
  38. ];
  39. export function UIScreens() {
  40. const theme = useTheme();
  41. const router = useRouter();
  42. const {selection} = usePageFilters();
  43. const location = useLocation();
  44. const organization = useOrganization();
  45. const {query: locationQuery} = location;
  46. const {
  47. primaryRelease,
  48. secondaryRelease,
  49. isLoading: isReleasesLoading,
  50. } = useReleaseSelection();
  51. const query = new MutableSearch(['transaction.op:ui.load']);
  52. const searchQuery = decodeScalar(locationQuery.query, '');
  53. if (searchQuery) {
  54. query.addStringFilter(prepareQueryForLandingPage(searchQuery, false));
  55. }
  56. const queryString = appendReleaseFilters(query, primaryRelease, secondaryRelease);
  57. // TODO: Replace with a default sort on the count column when added
  58. const orderby = decodeScalar(locationQuery.sort, '');
  59. const newQuery: NewQuery = {
  60. name: '',
  61. fields: [
  62. SpanMetricsField.PROJECT_ID,
  63. 'transaction',
  64. `avg_if(mobile.slow_frames,release,${primaryRelease})`,
  65. `avg_if(mobile.slow_frames,release,${secondaryRelease})`,
  66. `avg_if(mobile.frozen_frames,release,${primaryRelease})`,
  67. `avg_if(mobile.frozen_frames,release,${secondaryRelease})`,
  68. `avg_if(mobile.frames_delay,release,${primaryRelease})`,
  69. `avg_if(mobile.frames_delay,release,${secondaryRelease})`,
  70. `avg_compare(mobile.slow_frames,release,${primaryRelease},${secondaryRelease})`,
  71. `avg_compare(mobile.frozen_frames,release,${primaryRelease},${secondaryRelease})`,
  72. `avg_compare(mobile.frames_delay,release,${primaryRelease},${secondaryRelease})`,
  73. ],
  74. query: queryString,
  75. dataset: DiscoverDatasets.SPANS_METRICS,
  76. version: 2,
  77. projects: selection.projects,
  78. };
  79. newQuery.orderby = orderby;
  80. const tableEventView = EventView.fromNewQueryWithLocation(newQuery, location);
  81. const {
  82. data: topTransactionsData,
  83. isLoading: topTransactionsLoading,
  84. pageLinks,
  85. } = useTableQuery({
  86. eventView: tableEventView,
  87. enabled: !isReleasesLoading,
  88. referrer: Referrer.OVERVIEW_SCREENS_TABLE,
  89. });
  90. const topTransactions =
  91. topTransactionsData?.data?.slice(0, 5).map(datum => datum.transaction as string) ??
  92. [];
  93. // TODO: Fill with transaction.op filter
  94. const topEventsQuery = new MutableSearch([]);
  95. const topEventsQueryString = `${appendReleaseFilters(
  96. topEventsQuery,
  97. primaryRelease,
  98. secondaryRelease
  99. )} ${
  100. topTransactions.length > 0
  101. ? escapeFilterValue(
  102. `transaction:[${topTransactions.map(name => `"${name}"`).join()}]`
  103. )
  104. : ''
  105. }`.trim();
  106. const {data: releaseEvents, isLoading: isReleaseEventsLoading} = useTableQuery({
  107. eventView: EventView.fromNewQueryWithPageFilters(
  108. {
  109. name: '',
  110. fields: ['transaction', 'release', ...Y_AXIS_COLUMNS],
  111. yAxis: Y_AXIS_COLUMNS,
  112. query: topEventsQueryString,
  113. dataset: DiscoverDatasets.SPANS_METRICS,
  114. version: 2,
  115. },
  116. selection
  117. ),
  118. enabled: !topTransactionsLoading,
  119. referrer: Referrer.MOBILE_UI_BAR_CHART,
  120. });
  121. if (!defined(primaryRelease) && !isReleasesLoading) {
  122. return (
  123. <Alert type="warning" showIcon>
  124. {t(
  125. 'No screens found on recent releases. Please try a single iOS or Android project, a single environment or a smaller date range.'
  126. )}
  127. </Alert>
  128. );
  129. }
  130. const tableSearchFilters = new MutableSearch(['transaction.op:ui.load']);
  131. const derivedQuery = getTransactionSearchQuery(location, tableEventView.query);
  132. const transformedReleaseEvents = transformReleaseEvents({
  133. yAxes: Y_AXES,
  134. primaryRelease,
  135. secondaryRelease,
  136. colorPalette: theme.charts.getColorPalette(TOP_SCREENS - 2),
  137. releaseEvents,
  138. topTransactions,
  139. });
  140. return (
  141. <Layout>
  142. <ChartContainer>
  143. <TopScreensChart
  144. yAxis={YAXIS_COLUMNS[YAxis.SLOW_FRAMES]}
  145. isLoading={isReleaseEventsLoading}
  146. chartHeight={200}
  147. topTransactions={topTransactions}
  148. transformedReleaseEvents={transformedReleaseEvents}
  149. />
  150. <TopScreensChart
  151. yAxis={YAXIS_COLUMNS[YAxis.FROZEN_FRAMES]}
  152. isLoading={isReleaseEventsLoading}
  153. chartHeight={200}
  154. topTransactions={topTransactions}
  155. transformedReleaseEvents={transformedReleaseEvents}
  156. />
  157. <TopScreensChart
  158. yAxis={YAXIS_COLUMNS[YAxis.FRAMES_DELAY]}
  159. isLoading={isReleaseEventsLoading}
  160. chartHeight={200}
  161. topTransactions={topTransactions}
  162. transformedReleaseEvents={transformedReleaseEvents}
  163. />
  164. </ChartContainer>
  165. <SearchBar
  166. eventView={tableEventView}
  167. onSearch={search => {
  168. router.push({
  169. pathname: router.location.pathname,
  170. query: {
  171. ...location.query,
  172. cursor: undefined,
  173. query: String(search).trim() || undefined,
  174. },
  175. });
  176. }}
  177. organization={organization}
  178. query={getFreeTextFromQuery(derivedQuery)}
  179. placeholder={t('Search for Screen')}
  180. additionalConditions={
  181. new MutableSearch(
  182. appendReleaseFilters(tableSearchFilters, primaryRelease, secondaryRelease)
  183. )
  184. }
  185. />
  186. <UIScreensTable
  187. eventView={tableEventView}
  188. data={topTransactionsData}
  189. isLoading={topTransactionsLoading}
  190. pageLinks={pageLinks}
  191. />
  192. </Layout>
  193. );
  194. }
  195. const Layout = styled('div')`
  196. display: flex;
  197. flex-direction: column;
  198. gap: ${space(1)};
  199. `;
  200. const ChartContainer = styled('div')`
  201. display: grid;
  202. grid-template-columns: 33% 33% 33%;
  203. gap: ${space(1)};
  204. `;