index.tsx 8.3 KB

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