index.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import omit from 'lodash/omit';
  4. import Breadcrumbs from 'sentry/components/breadcrumbs';
  5. import ErrorBoundary from 'sentry/components/errorBoundary';
  6. import * as Layout from 'sentry/components/layouts/thirds';
  7. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  8. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {browserHistory} from 'sentry/utils/browserHistory';
  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 useRouter from 'sentry/utils/useRouter';
  17. import {SamplesTables} from 'sentry/views/performance/mobile/appStarts/screenSummary/samples';
  18. import {
  19. COLD_START_TYPE,
  20. StartTypeSelector,
  21. } from 'sentry/views/performance/mobile/appStarts/screenSummary/startTypeSelector';
  22. import {SpanSamplesPanel} from 'sentry/views/performance/mobile/components/spanSamplesPanel';
  23. import {MetricsRibbon} from 'sentry/views/performance/mobile/screenload/screenLoadSpans/metricsRibbon';
  24. import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders';
  25. import {useModuleBreadcrumbs} from 'sentry/views/performance/utils/useModuleBreadcrumbs';
  26. import {
  27. PRIMARY_RELEASE_ALIAS,
  28. ReleaseComparisonSelector,
  29. SECONDARY_RELEASE_ALIAS,
  30. } from 'sentry/views/starfish/components/releaseSelector';
  31. import {ModuleName, SpanMetricsField} from 'sentry/views/starfish/types';
  32. import {ROUTE_NAMES} from 'sentry/views/starfish/utils/routeNames';
  33. import AppStartWidgets from './widgets';
  34. type Query = {
  35. [SpanMetricsField.APP_START_TYPE]: string;
  36. 'device.class': string;
  37. primaryRelease: string;
  38. project: string;
  39. secondaryRelease: string;
  40. spanDescription: string;
  41. spanGroup: string;
  42. spanOp: string;
  43. transaction: string;
  44. };
  45. export function ScreenSummary() {
  46. const location = useLocation<Query>();
  47. const router = useRouter();
  48. const {
  49. primaryRelease,
  50. secondaryRelease,
  51. transaction: transactionName,
  52. spanGroup,
  53. spanDescription,
  54. spanOp,
  55. [SpanMetricsField.APP_START_TYPE]: appStartType,
  56. 'device.class': deviceClass,
  57. } = location.query;
  58. useEffect(() => {
  59. // Default the start type to cold start if not present
  60. if (!appStartType) {
  61. browserHistory.replace({
  62. ...location,
  63. query: {
  64. ...location.query,
  65. [SpanMetricsField.APP_START_TYPE]: COLD_START_TYPE,
  66. },
  67. });
  68. }
  69. }, [location, appStartType]);
  70. const crumbs = useModuleBreadcrumbs('app_start');
  71. return (
  72. <Layout.Page>
  73. <PageAlertProvider>
  74. <Layout.Header>
  75. <Layout.HeaderContent>
  76. <Breadcrumbs
  77. crumbs={[
  78. ...crumbs,
  79. {
  80. label: t('Screen Summary'),
  81. },
  82. ]}
  83. />
  84. <Layout.Title>{transactionName}</Layout.Title>
  85. </Layout.HeaderContent>
  86. </Layout.Header>
  87. <Layout.Body>
  88. <Layout.Main fullWidth>
  89. <PageAlert />
  90. <HeaderContainer>
  91. <ControlsContainer>
  92. <PageFilterBar condensed>
  93. <DatePageFilter />
  94. </PageFilterBar>
  95. <ReleaseComparisonSelector />
  96. <StartTypeSelector />
  97. </ControlsContainer>
  98. <MetricsRibbon
  99. dataset={DiscoverDatasets.SPANS_METRICS}
  100. filters={[
  101. `transaction:${transactionName}`,
  102. `span.op:app.start.${appStartType}`,
  103. '(',
  104. 'span.description:"Cold Start"',
  105. 'OR',
  106. 'span.description:"Warm Start"',
  107. ')',
  108. ]}
  109. fields={[
  110. `avg_if(span.duration,release,${primaryRelease})`,
  111. `avg_if(span.duration,release,${secondaryRelease})`,
  112. `avg_compare(span.duration,release,${primaryRelease},${secondaryRelease})`,
  113. 'count()',
  114. ]}
  115. blocks={[
  116. {
  117. unit: DurationUnit.MILLISECOND,
  118. allowZero: false,
  119. title:
  120. appStartType === COLD_START_TYPE
  121. ? t('Cold Start (%s)', PRIMARY_RELEASE_ALIAS)
  122. : t('Warm Start (%s)', PRIMARY_RELEASE_ALIAS),
  123. dataKey: `avg_if(span.duration,release,${primaryRelease})`,
  124. },
  125. {
  126. unit: DurationUnit.MILLISECOND,
  127. allowZero: false,
  128. title:
  129. appStartType === COLD_START_TYPE
  130. ? t('Cold Start (%s)', SECONDARY_RELEASE_ALIAS)
  131. : t('Warm Start (%s)', SECONDARY_RELEASE_ALIAS),
  132. dataKey: `avg_if(span.duration,release,${secondaryRelease})`,
  133. },
  134. {
  135. unit: 'percent_change',
  136. title: t('Change'),
  137. dataKey: `avg_compare(span.duration,release,${primaryRelease},${secondaryRelease})`,
  138. },
  139. {
  140. unit: 'count',
  141. title: t('Count'),
  142. dataKey: 'count()',
  143. },
  144. ]}
  145. referrer="api.starfish.mobile-startup-totals"
  146. />
  147. </HeaderContainer>
  148. <ErrorBoundary mini>
  149. <AppStartWidgets additionalFilters={[`transaction:${transactionName}`]} />
  150. </ErrorBoundary>
  151. <SamplesContainer>
  152. <SamplesTables transactionName={transactionName} />
  153. </SamplesContainer>
  154. {spanGroup && spanOp && appStartType && (
  155. <SpanSamplesPanel
  156. additionalFilters={{
  157. [SpanMetricsField.APP_START_TYPE]: appStartType,
  158. ...(deviceClass ? {[SpanMetricsField.DEVICE_CLASS]: deviceClass} : {}),
  159. }}
  160. groupId={spanGroup}
  161. moduleName={ModuleName.APP_START}
  162. transactionName={transactionName}
  163. spanDescription={spanDescription}
  164. spanOp={spanOp}
  165. onClose={() => {
  166. router.replace({
  167. pathname: router.location.pathname,
  168. query: omit(
  169. router.location.query,
  170. 'spanGroup',
  171. 'transactionMethod',
  172. 'spanDescription',
  173. 'spanOp'
  174. ),
  175. });
  176. }}
  177. />
  178. )}
  179. </Layout.Main>
  180. </Layout.Body>
  181. </PageAlertProvider>
  182. </Layout.Page>
  183. );
  184. }
  185. function PageWithProviders() {
  186. const location = useLocation<Query>();
  187. const {transaction} = location.query;
  188. return (
  189. <ModulePageProviders
  190. title={[transaction, ROUTE_NAMES['app-startup']].join(' — ')}
  191. features="spans-first-ui"
  192. >
  193. <ScreenSummary />
  194. </ModulePageProviders>
  195. );
  196. }
  197. export default PageWithProviders;
  198. const ControlsContainer = styled('div')`
  199. display: flex;
  200. gap: ${space(1.5)};
  201. `;
  202. const HeaderContainer = styled('div')`
  203. display: flex;
  204. flex-wrap: wrap;
  205. gap: ${space(2)};
  206. justify-content: space-between;
  207. `;
  208. const SamplesContainer = styled('div')`
  209. margin-top: ${space(2)};
  210. `;