index.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import styled from '@emotion/styled';
  2. import {Breadcrumbs} from 'sentry/components/breadcrumbs';
  3. import ButtonBar from 'sentry/components/buttonBar';
  4. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  7. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  8. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  9. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  10. import {t} from 'sentry/locale';
  11. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  12. import {useLocation} from 'sentry/utils/useLocation';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {useParams} from 'sentry/utils/useParams';
  15. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  16. import {Referrer} from 'sentry/views/performance/browser/resources/referrer';
  17. import ResourceInfo from 'sentry/views/performance/browser/resources/resourceSummaryPage/resourceInfo';
  18. import ResourceSummaryCharts from 'sentry/views/performance/browser/resources/resourceSummaryPage/resourceSummaryCharts';
  19. import ResourceSummaryTable from 'sentry/views/performance/browser/resources/resourceSummaryPage/resourceSummaryTable';
  20. import SampleImages from 'sentry/views/performance/browser/resources/resourceSummaryPage/sampleImages';
  21. import {FilterOptionsContainer} from 'sentry/views/performance/browser/resources/resourceView';
  22. import {IMAGE_FILE_EXTENSIONS} from 'sentry/views/performance/browser/resources/shared/constants';
  23. import RenderBlockingSelector from 'sentry/views/performance/browser/resources/shared/renderBlockingSelector';
  24. import {ResourceSpanOps} from 'sentry/views/performance/browser/resources/shared/types';
  25. import {useResourceModuleFilters} from 'sentry/views/performance/browser/resources/utils/useResourceFilters';
  26. import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders';
  27. import {useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover';
  28. import {SpanMetricsField} from 'sentry/views/starfish/types';
  29. import {SampleList} from 'sentry/views/starfish/views/spanSummaryPage/sampleList';
  30. const {
  31. SPAN_SELF_TIME,
  32. SPAN_DESCRIPTION,
  33. HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
  34. HTTP_RESPONSE_CONTENT_LENGTH,
  35. HTTP_RESPONSE_TRANSFER_SIZE,
  36. RESOURCE_RENDER_BLOCKING_STATUS,
  37. SPAN_OP,
  38. } = SpanMetricsField;
  39. function ResourceSummary() {
  40. const organization = useOrganization();
  41. const {groupId} = useParams();
  42. const filters = useResourceModuleFilters();
  43. const selectedSpanOp = filters[SPAN_OP];
  44. const {
  45. query: {transaction},
  46. } = useLocation();
  47. const {data} = useSpanMetrics(
  48. {
  49. search: MutableSearch.fromQueryObject({
  50. 'span.group': groupId,
  51. }),
  52. fields: [
  53. `avg(${SPAN_SELF_TIME})`,
  54. `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
  55. `avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`,
  56. `avg(${HTTP_RESPONSE_TRANSFER_SIZE})`,
  57. `sum(${SPAN_SELF_TIME})`,
  58. 'spm()',
  59. SPAN_OP,
  60. SPAN_DESCRIPTION,
  61. 'time_spent_percentage()',
  62. 'project.id',
  63. ],
  64. },
  65. Referrer.RESOURCE_SUMMARY_METRICS_RIBBON
  66. );
  67. const spanMetrics = selectedSpanOp
  68. ? data.find(item => item[SPAN_OP] === selectedSpanOp) ?? {}
  69. : data[0] ?? {};
  70. const uniqueSpanOps = new Set(data.map(item => item[SPAN_OP]));
  71. const isImage =
  72. filters[SPAN_OP] === ResourceSpanOps.IMAGE ||
  73. IMAGE_FILE_EXTENSIONS.includes(
  74. spanMetrics[SpanMetricsField.SPAN_DESCRIPTION]?.split('.').pop() || ''
  75. ) ||
  76. (uniqueSpanOps.size === 1 && spanMetrics[SPAN_OP] === ResourceSpanOps.IMAGE);
  77. return (
  78. <ModulePageProviders
  79. title={[t('Performance'), t('Resources'), t('Resource Summary')].join(' — ')}
  80. baseURL="/performance/browser/resources"
  81. features="spans-first-ui"
  82. >
  83. <Layout.Header>
  84. <Layout.HeaderContent>
  85. <Breadcrumbs
  86. crumbs={[
  87. {
  88. label: 'Performance',
  89. to: normalizeUrl(`/organizations/${organization.slug}/performance/`),
  90. preservePageFilters: true,
  91. },
  92. {
  93. label: 'Resources',
  94. to: normalizeUrl(
  95. `/organizations/${organization.slug}/performance/browser/resources/`
  96. ),
  97. preservePageFilters: true,
  98. },
  99. {
  100. label: 'Resource Summary',
  101. },
  102. ]}
  103. />
  104. <Layout.Title>{spanMetrics[SpanMetricsField.SPAN_DESCRIPTION]}</Layout.Title>
  105. </Layout.HeaderContent>
  106. <Layout.HeaderActions>
  107. <ButtonBar gap={1}>
  108. <FeedbackWidgetButton />
  109. </ButtonBar>
  110. </Layout.HeaderActions>
  111. </Layout.Header>
  112. <Layout.Body>
  113. <Layout.Main fullWidth>
  114. <HeaderContainer>
  115. <FilterOptionsContainer columnCount={2}>
  116. <PageFilterBar condensed>
  117. <ProjectPageFilter />
  118. <EnvironmentPageFilter />
  119. <DatePageFilter />
  120. </PageFilterBar>
  121. <RenderBlockingSelector
  122. value={filters[RESOURCE_RENDER_BLOCKING_STATUS] || ''}
  123. />
  124. </FilterOptionsContainer>
  125. <ResourceInfo
  126. avgContentLength={spanMetrics[`avg(${HTTP_RESPONSE_CONTENT_LENGTH})`]}
  127. avgDecodedContentLength={
  128. spanMetrics[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`]
  129. }
  130. avgTransferSize={spanMetrics[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`]}
  131. avgDuration={spanMetrics[`avg(${SPAN_SELF_TIME})`]}
  132. throughput={spanMetrics['spm()']}
  133. timeSpentTotal={spanMetrics[`sum(${SPAN_SELF_TIME})`]}
  134. timeSpentPercentage={spanMetrics[`time_spent_percentage()`]}
  135. />
  136. </HeaderContainer>
  137. {isImage && (
  138. <SampleImages groupId={groupId} projectId={data?.[0]?.['project.id']} />
  139. )}
  140. <ResourceSummaryCharts groupId={groupId} />
  141. <ResourceSummaryTable />
  142. <SampleList
  143. transactionRoute="/performance/browser/pageloads/"
  144. groupId={groupId}
  145. transactionName={transaction as string}
  146. additionalFields={[HTTP_RESPONSE_CONTENT_LENGTH]}
  147. />
  148. </Layout.Main>
  149. </Layout.Body>
  150. </ModulePageProviders>
  151. );
  152. }
  153. const HeaderContainer = styled('div')`
  154. display: flex;
  155. justify-content: space-between;
  156. flex-wrap: wrap;
  157. `;
  158. export default ResourceSummary;