index.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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/organization';
  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 {AverageComparisonChart} from 'sentry/views/performance/mobile/appStarts/screens/averageComparisonChart';
  19. import {CountChart} from 'sentry/views/performance/mobile/appStarts/screens/countChart';
  20. import {AppStartScreens} from 'sentry/views/performance/mobile/appStarts/screens/screensTable';
  21. import {COLD_START_TYPE} from 'sentry/views/performance/mobile/appStarts/screenSummary/startTypeSelector';
  22. import {TOP_SCREENS} from 'sentry/views/performance/mobile/constants';
  23. import {
  24. getFreeTextFromQuery,
  25. YAxis,
  26. YAXIS_COLUMNS,
  27. } from 'sentry/views/performance/mobile/screenload/screens';
  28. import {ScreensBarChart} from 'sentry/views/performance/mobile/screenload/screens/screenBarChart';
  29. import {useTableQuery} from 'sentry/views/performance/mobile/screenload/screens/screensTable';
  30. import {transformReleaseEvents} from 'sentry/views/performance/mobile/screenload/screens/utils';
  31. import useCrossPlatformProject from 'sentry/views/performance/mobile/useCrossPlatformProject';
  32. import useTruncatedReleaseNames from 'sentry/views/performance/mobile/useTruncatedRelease';
  33. import {getTransactionSearchQuery} from 'sentry/views/performance/utils';
  34. import {useReleaseSelection} from 'sentry/views/starfish/queries/useReleases';
  35. import {SpanMetricsField} from 'sentry/views/starfish/types';
  36. import {appendReleaseFilters} from 'sentry/views/starfish/utils/releaseComparison';
  37. const Y_AXES = [YAxis.COLD_START, YAxis.WARM_START];
  38. const Y_AXIS_COLS = [YAXIS_COLUMNS[YAxis.COLD_START], YAXIS_COLUMNS[YAxis.WARM_START]];
  39. type Props = {
  40. additionalFilters?: string[];
  41. chartHeight?: number;
  42. };
  43. function AppStartup({additionalFilters, chartHeight}: Props) {
  44. const theme = useTheme();
  45. const pageFilter = usePageFilters();
  46. const {selection} = pageFilter;
  47. const location = useLocation();
  48. const organization = useOrganization();
  49. const {query: locationQuery} = location;
  50. const {
  51. primaryRelease,
  52. secondaryRelease,
  53. isLoading: isReleasesLoading,
  54. } = useReleaseSelection();
  55. const {truncatedPrimaryRelease, truncatedSecondaryRelease} = useTruncatedReleaseNames();
  56. const {isProjectCrossPlatform, selectedPlatform} = useCrossPlatformProject();
  57. const router = useRouter();
  58. const appStartType =
  59. decodeScalar(location.query[SpanMetricsField.APP_START_TYPE]) ?? COLD_START_TYPE;
  60. const query = new MutableSearch([
  61. 'event.type:transaction',
  62. 'transaction.op:ui.load',
  63. `count_starts(measurements.app_start_${appStartType}):>0`,
  64. ...(additionalFilters ?? []),
  65. ]);
  66. if (isProjectCrossPlatform) {
  67. query.addFilterValue('os.name', selectedPlatform);
  68. }
  69. const searchQuery = decodeScalar(locationQuery.query, '');
  70. if (searchQuery) {
  71. query.addStringFilter(prepareQueryForLandingPage(searchQuery, false));
  72. }
  73. const queryString = appendReleaseFilters(query, primaryRelease, secondaryRelease);
  74. const sortCountField = `count_starts_measurements_app_start_${appStartType}`;
  75. const orderby = decodeScalar(locationQuery.sort, `-${sortCountField}`);
  76. const newQuery: NewQuery = {
  77. name: '',
  78. fields: [
  79. 'transaction',
  80. SpanMetricsField.PROJECT_ID,
  81. `avg_if(measurements.app_start_${appStartType},release,${primaryRelease})`,
  82. `avg_if(measurements.app_start_${appStartType},release,${secondaryRelease})`,
  83. `avg_compare(measurements.app_start_${appStartType},release,${primaryRelease},${secondaryRelease})`,
  84. 'count_starts(measurements.app_start_cold)',
  85. 'count_starts(measurements.app_start_warm)',
  86. ],
  87. query: queryString,
  88. dataset: DiscoverDatasets.METRICS,
  89. version: 2,
  90. projects: selection.projects,
  91. };
  92. newQuery.orderby = orderby;
  93. const tableEventView = EventView.fromNewQueryWithLocation(newQuery, location);
  94. const {
  95. data: topTransactionsData,
  96. isLoading: topTransactionsLoading,
  97. pageLinks,
  98. } = useTableQuery({
  99. eventView: tableEventView,
  100. enabled: !isReleasesLoading,
  101. referrer: 'api.starfish.mobile-startup-screen-table',
  102. });
  103. const topTransactions =
  104. topTransactionsData?.data?.slice(0, 5).map(datum => datum.transaction as string) ??
  105. [];
  106. const topEventsQuery = new MutableSearch([
  107. 'event.type:transaction',
  108. 'transaction.op:ui.load',
  109. ...(additionalFilters ?? []),
  110. ]);
  111. if (isProjectCrossPlatform) {
  112. topEventsQuery.addFilterValue('os.name', selectedPlatform);
  113. }
  114. const topEventsQueryString = `${appendReleaseFilters(
  115. topEventsQuery,
  116. primaryRelease,
  117. secondaryRelease
  118. )} ${
  119. topTransactions.length > 0
  120. ? escapeFilterValue(
  121. `transaction:[${topTransactions.map(name => `"${name}"`).join()}]`
  122. )
  123. : ''
  124. }`.trim();
  125. const {data: releaseEvents, isLoading: isReleaseEventsLoading} = useTableQuery({
  126. eventView: EventView.fromNewQueryWithPageFilters(
  127. {
  128. name: '',
  129. fields: ['transaction', 'release', ...Y_AXIS_COLS],
  130. orderby: Y_AXIS_COLS[0],
  131. yAxis: Y_AXIS_COLS,
  132. query: topEventsQueryString,
  133. dataset: DiscoverDatasets.METRICS,
  134. version: 2,
  135. },
  136. selection
  137. ),
  138. enabled: !topTransactionsLoading,
  139. referrer: 'api.starfish.mobile-startup-bar-chart',
  140. });
  141. if (!defined(primaryRelease) && !isReleasesLoading) {
  142. return (
  143. <Alert type="warning" showIcon>
  144. {t(
  145. 'No screens found on recent releases. Please try a single iOS or Android project, a single environment or a smaller date range.'
  146. )}
  147. </Alert>
  148. );
  149. }
  150. const derivedQuery = getTransactionSearchQuery(location, tableEventView.query);
  151. const tableSearchFilters = new MutableSearch([
  152. 'event.type:transaction',
  153. 'transaction.op:ui.load',
  154. ]);
  155. const transformedReleaseEvents = transformReleaseEvents({
  156. yAxes: Y_AXES,
  157. primaryRelease,
  158. secondaryRelease,
  159. colorPalette: theme.charts.getColorPalette(TOP_SCREENS - 2),
  160. releaseEvents,
  161. topTransactions,
  162. });
  163. const countTopScreens = Math.min(TOP_SCREENS, topTransactions.length);
  164. const [singularTopScreenTitle, pluralTopScreenTitle] =
  165. appStartType === COLD_START_TYPE
  166. ? [t('Top Screen Cold Start'), t('Top %s Screen Cold Starts', countTopScreens)]
  167. : [t('Top Screen Warm Start'), t('Top %s Screen Warm Starts', countTopScreens)];
  168. const yAxis =
  169. YAXIS_COLUMNS[appStartType === COLD_START_TYPE ? YAxis.COLD_START : YAxis.WARM_START];
  170. return (
  171. <div data-test-id="starfish-mobile-app-startup-view">
  172. <ChartContainer>
  173. <AverageComparisonChart chartHeight={chartHeight} />
  174. <ScreensBarChart
  175. chartOptions={[
  176. {
  177. title: countTopScreens > 1 ? pluralTopScreenTitle : singularTopScreenTitle,
  178. yAxis,
  179. xAxisLabel: topTransactions,
  180. series: Object.values(transformedReleaseEvents[yAxis]),
  181. subtitle: primaryRelease
  182. ? t(
  183. '%s v. %s',
  184. truncatedPrimaryRelease,
  185. secondaryRelease ? truncatedSecondaryRelease : ''
  186. )
  187. : '',
  188. },
  189. ]}
  190. chartHeight={chartHeight}
  191. isLoading={isReleaseEventsLoading || isReleasesLoading}
  192. chartKey={`${appStartType}Start`}
  193. />
  194. <CountChart chartHeight={chartHeight} />
  195. </ChartContainer>
  196. <StyledSearchBar
  197. eventView={tableEventView}
  198. onSearch={search => {
  199. router.push({
  200. pathname: router.location.pathname,
  201. query: {
  202. ...location.query,
  203. cursor: undefined,
  204. query: String(search).trim() || undefined,
  205. },
  206. });
  207. }}
  208. organization={organization}
  209. query={getFreeTextFromQuery(derivedQuery)}
  210. placeholder={t('Search for Screen')}
  211. additionalConditions={
  212. new MutableSearch(
  213. appendReleaseFilters(tableSearchFilters, primaryRelease, secondaryRelease)
  214. )
  215. }
  216. />
  217. <AppStartScreens
  218. eventView={tableEventView}
  219. data={topTransactionsData}
  220. isLoading={topTransactionsLoading}
  221. pageLinks={pageLinks}
  222. />
  223. </div>
  224. );
  225. }
  226. export default AppStartup;
  227. const StyledSearchBar = styled(SearchBar)`
  228. margin-bottom: ${space(1)};
  229. `;
  230. const ChartContainer = styled('div')`
  231. display: grid;
  232. grid-template-columns: 33% 33% 33%;
  233. gap: ${space(1)};
  234. `;