mobileOverviewPage.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import styled from '@emotion/styled';
  2. import Feature from 'sentry/components/acl/feature';
  3. import * as Layout from 'sentry/components/layouts/thirds';
  4. import {NoAccess} from 'sentry/components/noAccess';
  5. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  6. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  7. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  8. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  9. import TransactionNameSearchBar from 'sentry/components/performance/searchBar';
  10. import * as TeamKeyTransactionManager from 'sentry/components/performance/teamKeyTransactionsManager';
  11. import type {Project} from 'sentry/types/project';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import {
  14. canUseMetricsData,
  15. useMEPSettingContext,
  16. } from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  17. import {PageAlert, usePageAlert} from 'sentry/utils/performance/contexts/pageAlert';
  18. import {PerformanceDisplayProvider} from 'sentry/utils/performance/contexts/performanceDisplayContext';
  19. import {getSelectedProjectList} from 'sentry/utils/project/useSelectedProjectsHaveField';
  20. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  21. import {useLocation} from 'sentry/utils/useLocation';
  22. import {useNavigate} from 'sentry/utils/useNavigate';
  23. import useOrganization from 'sentry/utils/useOrganization';
  24. import usePageFilters from 'sentry/utils/usePageFilters';
  25. import useProjects from 'sentry/utils/useProjects';
  26. import {useUserTeams} from 'sentry/utils/useUserTeams';
  27. import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
  28. import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon';
  29. import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject';
  30. import {ViewTrendsButton} from 'sentry/views/insights/common/viewTrendsButton';
  31. import {DomainOverviewPageProviders} from 'sentry/views/insights/pages/domainOverviewPageProviders';
  32. import {MobileHeader} from 'sentry/views/insights/pages/mobile/mobilePageHeader';
  33. import {
  34. MOBILE_LANDING_TITLE,
  35. MOBILE_PLATFORMS,
  36. OVERVIEW_PAGE_ALLOWED_OPS,
  37. } from 'sentry/views/insights/pages/mobile/settings';
  38. import {
  39. generateGenericPerformanceEventView,
  40. generateMobilePerformanceEventView,
  41. USER_MISERY_TOOLTIP,
  42. } from 'sentry/views/performance/data';
  43. import {checkIsReactNative} from 'sentry/views/performance/landing/utils';
  44. import {
  45. DoubleChartRow,
  46. TripleChartRow,
  47. } from 'sentry/views/performance/landing/widgets/components/widgetChartRow';
  48. import {filterAllowedChartsMetrics} from 'sentry/views/performance/landing/widgets/utils';
  49. import {PerformanceWidgetSetting} from 'sentry/views/performance/landing/widgets/widgetDefinitions';
  50. import {LegacyOnboarding} from 'sentry/views/performance/onboarding';
  51. import Table from 'sentry/views/performance/table';
  52. import {
  53. getTransactionSearchQuery,
  54. ProjectPerformanceType,
  55. } from 'sentry/views/performance/utils';
  56. const MOBILE_COLUMN_TITLES = [
  57. {title: 'transaction'},
  58. {title: 'operation'},
  59. {title: 'project'},
  60. {title: 'tpm'},
  61. {title: 'slow frame %'},
  62. {title: 'frozen frame %'},
  63. {title: 'users'},
  64. {title: 'user misery', tooltip: USER_MISERY_TOOLTIP},
  65. ];
  66. const REACT_NATIVE_COLUMN_TITLES = [
  67. {title: 'transaction'},
  68. {title: 'operation'},
  69. {title: 'project'},
  70. {title: 'tpm'},
  71. {title: 'slow frame %'},
  72. {title: 'frozen frame %'},
  73. {title: 'stall %'},
  74. {title: 'users'},
  75. {title: 'user misery'},
  76. ];
  77. function MobileOverviewPage() {
  78. const organization = useOrganization();
  79. const location = useLocation();
  80. const {setPageError} = usePageAlert();
  81. const {projects} = useProjects();
  82. const onboardingProject = useOnboardingProject();
  83. const navigate = useNavigate();
  84. const {teams} = useUserTeams();
  85. const mepSetting = useMEPSettingContext();
  86. const {selection} = usePageFilters();
  87. const withStaticFilters = canUseMetricsData(organization);
  88. const eventView = generateMobilePerformanceEventView(
  89. location,
  90. projects,
  91. generateGenericPerformanceEventView(location, withStaticFilters, organization),
  92. withStaticFilters,
  93. organization
  94. );
  95. const searchBarEventView = eventView.clone();
  96. let columnTitles = checkIsReactNative(eventView)
  97. ? REACT_NATIVE_COLUMN_TITLES
  98. : MOBILE_COLUMN_TITLES;
  99. const doubleChartRowEventView = eventView.clone(); // some of the double chart rows rely on span metrics, so they can't be queried the same way
  100. const selectedMobileProjects: Project[] = getSelectedProjectList(
  101. selection.projects,
  102. projects
  103. ).filter((project): project is Project =>
  104. Boolean(project?.platform && MOBILE_PLATFORMS.includes(project.platform))
  105. );
  106. const existingQuery = new MutableSearch(eventView.query);
  107. existingQuery.addDisjunctionFilterValues('transaction.op', OVERVIEW_PAGE_ALLOWED_OPS);
  108. if (selectedMobileProjects.length > 0) {
  109. existingQuery.addOp('OR');
  110. existingQuery.addFilterValue(
  111. 'project.id',
  112. `[${selectedMobileProjects.map(({id}) => id).join(',')}]`
  113. );
  114. }
  115. eventView.query = existingQuery.formatString();
  116. const showOnboarding = onboardingProject !== undefined;
  117. const doubleChartRowCharts = [
  118. PerformanceWidgetSetting.MOST_SLOW_FRAMES,
  119. PerformanceWidgetSetting.MOST_FROZEN_FRAMES,
  120. ];
  121. const tripleChartRowCharts = filterAllowedChartsMetrics(
  122. organization,
  123. [
  124. PerformanceWidgetSetting.TPM_AREA,
  125. PerformanceWidgetSetting.DURATION_HISTOGRAM,
  126. PerformanceWidgetSetting.P50_DURATION_AREA,
  127. PerformanceWidgetSetting.P75_DURATION_AREA,
  128. PerformanceWidgetSetting.P95_DURATION_AREA,
  129. PerformanceWidgetSetting.P99_DURATION_AREA,
  130. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  131. ],
  132. mepSetting
  133. );
  134. if (organization.features.includes('mobile-vitals')) {
  135. columnTitles = [
  136. ...columnTitles.slice(0, 5),
  137. {title: 'ttid'},
  138. ...columnTitles.slice(5, 0),
  139. ];
  140. tripleChartRowCharts.push(
  141. ...[
  142. PerformanceWidgetSetting.TIME_TO_INITIAL_DISPLAY,
  143. PerformanceWidgetSetting.TIME_TO_FULL_DISPLAY,
  144. ]
  145. );
  146. }
  147. if (organization.features.includes('insights-initial-modules')) {
  148. doubleChartRowCharts[0] = PerformanceWidgetSetting.SLOW_SCREENS_BY_TTID;
  149. }
  150. if (organization.features.includes('starfish-mobile-appstart')) {
  151. doubleChartRowCharts.push(
  152. PerformanceWidgetSetting.SLOW_SCREENS_BY_COLD_START,
  153. PerformanceWidgetSetting.SLOW_SCREENS_BY_WARM_START
  154. );
  155. }
  156. if (organization.features.includes('insights-initial-modules')) {
  157. doubleChartRowCharts.push(PerformanceWidgetSetting.MOST_TIME_CONSUMING_DOMAINS);
  158. }
  159. const sharedProps = {eventView, location, organization, withStaticFilters};
  160. const getFreeTextFromQuery = (query: string) => {
  161. const conditions = new MutableSearch(query);
  162. const transactionValues = conditions.getFilterValues('transaction');
  163. if (transactionValues.length) {
  164. return transactionValues[0];
  165. }
  166. if (conditions.freeText.length > 0) {
  167. // raw text query will be wrapped in wildcards in generatePerformanceEventView
  168. // so no need to wrap it here
  169. return conditions.freeText.join(' ');
  170. }
  171. return '';
  172. };
  173. function handleSearch(searchQuery: string) {
  174. trackAnalytics('performance.domains.mobile.search', {organization});
  175. navigate({
  176. pathname: location.pathname,
  177. query: {
  178. ...location.query,
  179. cursor: undefined,
  180. query: String(searchQuery).trim() || undefined,
  181. isDefaultQuery: false,
  182. },
  183. });
  184. }
  185. const derivedQuery = getTransactionSearchQuery(location, eventView.query);
  186. return (
  187. <Feature
  188. features="performance-view"
  189. organization={organization}
  190. renderDisabled={NoAccess}
  191. >
  192. <MobileHeader
  193. headerTitle={MOBILE_LANDING_TITLE}
  194. headerActions={<ViewTrendsButton />}
  195. />
  196. <Layout.Body>
  197. <Layout.Main fullWidth>
  198. <ModuleLayout.Layout>
  199. <ModuleLayout.Full>
  200. <ToolRibbon>
  201. <PageFilterBar condensed>
  202. <ProjectPageFilter />
  203. <EnvironmentPageFilter />
  204. <DatePageFilter />
  205. </PageFilterBar>
  206. {!showOnboarding && (
  207. <StyledTransactionNameSearchBar
  208. organization={organization}
  209. eventView={searchBarEventView}
  210. onSearch={(query: string) => {
  211. handleSearch(query);
  212. }}
  213. query={getFreeTextFromQuery(derivedQuery)!}
  214. />
  215. )}
  216. </ToolRibbon>
  217. </ModuleLayout.Full>
  218. <PageAlert />
  219. <ModuleLayout.Full>
  220. {!showOnboarding && (
  221. <PerformanceDisplayProvider
  222. value={{performanceType: ProjectPerformanceType.MOBILE}}
  223. >
  224. <TeamKeyTransactionManager.Provider
  225. organization={organization}
  226. teams={teams}
  227. selectedTeams={['myteams']}
  228. selectedProjects={eventView.project.map(String)}
  229. >
  230. <DoubleChartRow
  231. allowedCharts={doubleChartRowCharts}
  232. {...sharedProps}
  233. eventView={doubleChartRowEventView}
  234. />
  235. <TripleChartRow
  236. allowedCharts={tripleChartRowCharts}
  237. {...sharedProps}
  238. />
  239. <Table
  240. projects={projects}
  241. columnTitles={columnTitles}
  242. setError={setPageError}
  243. {...sharedProps}
  244. />
  245. </TeamKeyTransactionManager.Provider>
  246. </PerformanceDisplayProvider>
  247. )}
  248. {showOnboarding && (
  249. <LegacyOnboarding
  250. project={onboardingProject}
  251. organization={organization}
  252. />
  253. )}
  254. </ModuleLayout.Full>
  255. </ModuleLayout.Layout>
  256. </Layout.Main>
  257. </Layout.Body>
  258. </Feature>
  259. );
  260. }
  261. function MobileOverviewPageWithProviders() {
  262. return (
  263. <DomainOverviewPageProviders>
  264. <MobileOverviewPage />
  265. </DomainOverviewPageProviders>
  266. );
  267. }
  268. const StyledTransactionNameSearchBar = styled(TransactionNameSearchBar)`
  269. flex: 2;
  270. `;
  271. export default MobileOverviewPageWithProviders;