index.tsx 6.3 KB

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