index.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import omit from 'lodash/omit';
  4. import Breadcrumbs from 'sentry/components/breadcrumbs';
  5. import ButtonBar from 'sentry/components/buttonBar';
  6. import ErrorBoundary from 'sentry/components/errorBoundary';
  7. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  8. import * as Layout from 'sentry/components/layouts/thirds';
  9. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  10. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  11. import {t} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import {DurationUnit} from 'sentry/utils/discover/fields';
  14. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  15. import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
  16. import {useLocation} from 'sentry/utils/useLocation';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import useProjects from 'sentry/utils/useProjects';
  19. import useRouter from 'sentry/utils/useRouter';
  20. import {SpanSamplesPanel} from 'sentry/views/performance/mobile/components/spanSamplesPanel';
  21. import {
  22. ScreenCharts,
  23. YAxis,
  24. } from 'sentry/views/performance/mobile/screenload/screenLoadSpans/charts';
  25. import {ScreenLoadEventSamples} from 'sentry/views/performance/mobile/screenload/screenLoadSpans/eventSamples';
  26. import {MetricsRibbon} from 'sentry/views/performance/mobile/screenload/screenLoadSpans/metricsRibbon';
  27. import {ScreenLoadSpansTable} from 'sentry/views/performance/mobile/screenload/screenLoadSpans/table';
  28. import {
  29. MobileCursors,
  30. MobileSortKeys,
  31. } from 'sentry/views/performance/mobile/screenload/screens/constants';
  32. import {PlatformSelector} from 'sentry/views/performance/mobile/screenload/screens/platformSelector';
  33. import {isCrossPlatform} from 'sentry/views/performance/mobile/screenload/screens/utils';
  34. import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders';
  35. import {useModuleBreadcrumbs} from 'sentry/views/performance/utils/useModuleBreadcrumbs';
  36. import {
  37. PRIMARY_RELEASE_ALIAS,
  38. ReleaseComparisonSelector,
  39. SECONDARY_RELEASE_ALIAS,
  40. } from 'sentry/views/starfish/components/releaseSelector';
  41. import {ModuleName} from 'sentry/views/starfish/types';
  42. import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
  43. type Query = {
  44. primaryRelease: string;
  45. project: string;
  46. secondaryRelease: string;
  47. spanGroup: string;
  48. transaction: string;
  49. [QueryParameterNames.SPANS_SORT]: string;
  50. spanDescription?: string;
  51. };
  52. function ScreenLoadSpans() {
  53. const location = useLocation<Query>();
  54. const organization = useOrganization();
  55. const router = useRouter();
  56. const {projects} = useProjects();
  57. const project = useMemo(() => {
  58. return projects.find(p => p.id === location.query.project);
  59. }, [location.query.project, projects]);
  60. const crumbs = useModuleBreadcrumbs('screen_load');
  61. const {
  62. spanGroup,
  63. primaryRelease,
  64. secondaryRelease,
  65. transaction: transactionName,
  66. spanDescription,
  67. } = location.query;
  68. return (
  69. <Layout.Page>
  70. <PageAlertProvider>
  71. <Layout.Header>
  72. <Layout.HeaderContent>
  73. <Breadcrumbs
  74. crumbs={[
  75. ...crumbs,
  76. {
  77. label: t('Screen Summary'),
  78. },
  79. ]}
  80. />
  81. <HeaderWrapper>
  82. <Layout.Title>{transactionName}</Layout.Title>
  83. {organization.features.includes('spans-first-ui') &&
  84. project &&
  85. isCrossPlatform(project) && <PlatformSelector />}
  86. </HeaderWrapper>
  87. </Layout.HeaderContent>
  88. <Layout.HeaderActions>
  89. <ButtonBar gap={1}>
  90. <FeedbackWidgetButton />
  91. </ButtonBar>
  92. </Layout.HeaderActions>
  93. </Layout.Header>
  94. <Layout.Body>
  95. <Layout.Main fullWidth>
  96. <PageAlert />
  97. <Container>
  98. <FilterContainer>
  99. <PageFilterBar condensed>
  100. <DatePageFilter />
  101. </PageFilterBar>
  102. <ReleaseComparisonSelector />
  103. </FilterContainer>
  104. <MetricsRibbon
  105. dataset={DiscoverDatasets.METRICS}
  106. filters={[
  107. 'event.type:transaction',
  108. 'transaction.op:ui.load',
  109. `transaction:${transactionName}`,
  110. ]}
  111. fields={[
  112. `avg_if(measurements.time_to_initial_display,release,${primaryRelease})`,
  113. `avg_if(measurements.time_to_initial_display,release,${secondaryRelease})`,
  114. `avg_if(measurements.time_to_full_display,release,${primaryRelease})`,
  115. `avg_if(measurements.time_to_full_display,release,${secondaryRelease})`,
  116. 'count()',
  117. ]}
  118. blocks={[
  119. {
  120. unit: DurationUnit.MILLISECOND,
  121. dataKey: `avg_if(measurements.time_to_initial_display,release,${primaryRelease})`,
  122. title: t('TTID (%s)', PRIMARY_RELEASE_ALIAS),
  123. },
  124. {
  125. unit: DurationUnit.MILLISECOND,
  126. dataKey: `avg_if(measurements.time_to_initial_display,release,${secondaryRelease})`,
  127. title: t('TTID (%s)', SECONDARY_RELEASE_ALIAS),
  128. },
  129. {
  130. unit: DurationUnit.MILLISECOND,
  131. dataKey: `avg_if(measurements.time_to_full_display,release,${primaryRelease})`,
  132. title: t('TTFD (%s)', PRIMARY_RELEASE_ALIAS),
  133. },
  134. {
  135. unit: DurationUnit.MILLISECOND,
  136. dataKey: `avg_if(measurements.time_to_full_display,release,${secondaryRelease})`,
  137. title: t('TTFD (%s)', SECONDARY_RELEASE_ALIAS),
  138. },
  139. {
  140. unit: 'count',
  141. dataKey: 'count()',
  142. title: t('Count'),
  143. },
  144. ]}
  145. referrer="api.starfish.mobile-screen-totals"
  146. />
  147. </Container>
  148. <ErrorBoundary mini>
  149. <ScreenCharts
  150. yAxes={[YAxis.TTID, YAxis.TTFD, YAxis.COUNT]}
  151. additionalFilters={[`transaction:${transactionName}`]}
  152. chartHeight={120}
  153. project={project}
  154. />
  155. <SampleContainer>
  156. <SampleContainerItem>
  157. <ScreenLoadEventSamples
  158. release={primaryRelease}
  159. sortKey={MobileSortKeys.RELEASE_1_EVENT_SAMPLE_TABLE}
  160. cursorName={MobileCursors.RELEASE_1_EVENT_SAMPLE_TABLE}
  161. transaction={transactionName}
  162. showDeviceClassSelector
  163. project={project}
  164. />
  165. </SampleContainerItem>
  166. <SampleContainerItem>
  167. <ScreenLoadEventSamples
  168. release={secondaryRelease}
  169. sortKey={MobileSortKeys.RELEASE_2_EVENT_SAMPLE_TABLE}
  170. cursorName={MobileCursors.RELEASE_2_EVENT_SAMPLE_TABLE}
  171. transaction={transactionName}
  172. project={project}
  173. />
  174. </SampleContainerItem>
  175. </SampleContainer>
  176. <ScreenLoadSpansTable
  177. transaction={transactionName}
  178. primaryRelease={primaryRelease}
  179. secondaryRelease={secondaryRelease}
  180. project={project}
  181. />
  182. {spanGroup && (
  183. <SpanSamplesPanel
  184. groupId={spanGroup}
  185. moduleName={ModuleName.SCREEN_LOAD}
  186. transactionName={transactionName}
  187. spanDescription={spanDescription}
  188. onClose={() => {
  189. router.replace({
  190. pathname: router.location.pathname,
  191. query: omit(
  192. router.location.query,
  193. 'spanGroup',
  194. 'transactionMethod'
  195. ),
  196. });
  197. }}
  198. />
  199. )}
  200. </ErrorBoundary>
  201. </Layout.Main>
  202. </Layout.Body>
  203. </PageAlertProvider>
  204. </Layout.Page>
  205. );
  206. }
  207. function PageWithProviders() {
  208. const location = useLocation<Query>();
  209. const {transaction} = location.query;
  210. return (
  211. <ModulePageProviders
  212. title={[transaction, t('Screen Loads')].join(' — ')}
  213. features="spans-first-ui"
  214. >
  215. <ScreenLoadSpans />
  216. </ModulePageProviders>
  217. );
  218. }
  219. export default PageWithProviders;
  220. const Container = styled('div')`
  221. display: grid;
  222. grid-template-rows: 1fr 1fr;
  223. grid-template-columns: 1fr;
  224. column-gap: ${space(2)};
  225. @media (min-width: ${p => p.theme.breakpoints.large}) {
  226. grid-template-rows: auto;
  227. grid-template-columns: auto minmax(100px, max-content);
  228. }
  229. `;
  230. const FilterContainer = styled('div')`
  231. display: grid;
  232. column-gap: ${space(1)};
  233. grid-template-rows: auto;
  234. grid-template-columns: auto 1fr;
  235. `;
  236. const SampleContainer = styled('div')`
  237. display: flex;
  238. flex-direction: row;
  239. flex-wrap: wrap;
  240. gap: ${space(2)};
  241. `;
  242. const SampleContainerItem = styled('div')`
  243. flex: 1;
  244. `;
  245. const HeaderWrapper = styled('div')`
  246. display: flex;
  247. `;