index.tsx 9.0 KB

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