resourceSummaryPage.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import React from 'react';
  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, tct} from 'sentry/locale';
  11. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  12. import {useLocation} from 'sentry/utils/useLocation';
  13. import {useParams} from 'sentry/utils/useParams';
  14. import ResourceSummaryCharts from 'sentry/views/insights/browser/resources/components/charts/resourceSummaryCharts';
  15. import RenderBlockingSelector from 'sentry/views/insights/browser/resources/components/renderBlockingSelector';
  16. import ResourceInfo from 'sentry/views/insights/browser/resources/components/resourceInfo';
  17. import SampleImages from 'sentry/views/insights/browser/resources/components/sampleImages';
  18. import ResourceSummaryTable from 'sentry/views/insights/browser/resources/components/tables/resourceSummaryTable';
  19. import {IMAGE_FILE_EXTENSIONS} from 'sentry/views/insights/browser/resources/constants';
  20. import {Referrer} from 'sentry/views/insights/browser/resources/referrer';
  21. import {DATA_TYPE} from 'sentry/views/insights/browser/resources/settings';
  22. import {ResourceSpanOps} from 'sentry/views/insights/browser/resources/types';
  23. import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
  24. import {HeaderContainer} from 'sentry/views/insights/common/components/headerContainer';
  25. import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
  26. import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';
  27. import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper';
  28. import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon';
  29. import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
  30. import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs';
  31. import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL';
  32. import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector';
  33. import {SampleList} from 'sentry/views/insights/common/views/spanSummaryPage/sampleList';
  34. import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader';
  35. import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
  36. import {ModuleName, SpanMetricsField} from 'sentry/views/insights/types';
  37. import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs';
  38. const {
  39. SPAN_SELF_TIME,
  40. SPAN_DESCRIPTION,
  41. HTTP_DECODED_RESPONSE_CONTENT_LENGTH,
  42. HTTP_RESPONSE_CONTENT_LENGTH,
  43. HTTP_RESPONSE_TRANSFER_SIZE,
  44. RESOURCE_RENDER_BLOCKING_STATUS,
  45. SPAN_OP,
  46. } = SpanMetricsField;
  47. function ResourceSummary() {
  48. const webVitalsModuleURL = useModuleURL('vital');
  49. const {groupId} = useParams();
  50. const {isInDomainView} = useDomainViewFilters();
  51. const filters = useResourceModuleFilters();
  52. const selectedSpanOp = filters[SPAN_OP];
  53. const {
  54. query: {transaction},
  55. } = useLocation();
  56. const {data, isPending} = useSpanMetrics(
  57. {
  58. search: MutableSearch.fromQueryObject({
  59. 'span.group': groupId,
  60. ...(filters[SpanMetricsField.USER_GEO_SUBREGION]
  61. ? {
  62. [SpanMetricsField.USER_GEO_SUBREGION]: `[${filters[SpanMetricsField.USER_GEO_SUBREGION].join(',')}]`,
  63. }
  64. : {}),
  65. }),
  66. fields: [
  67. `avg(${SPAN_SELF_TIME})`,
  68. `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
  69. `avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`,
  70. `avg(${HTTP_RESPONSE_TRANSFER_SIZE})`,
  71. `sum(${SPAN_SELF_TIME})`,
  72. 'spm()',
  73. SPAN_OP,
  74. SPAN_DESCRIPTION,
  75. 'time_spent_percentage()',
  76. 'project.id',
  77. ],
  78. },
  79. Referrer.RESOURCE_SUMMARY_METRICS_RIBBON
  80. );
  81. const spanMetrics = selectedSpanOp
  82. ? data.find(item => item[SPAN_OP] === selectedSpanOp) ?? {}
  83. : data[0] ?? {};
  84. const uniqueSpanOps = new Set(data.map(item => item[SPAN_OP]));
  85. const isImage =
  86. filters[SPAN_OP] === ResourceSpanOps.IMAGE ||
  87. IMAGE_FILE_EXTENSIONS.includes(
  88. spanMetrics[SpanMetricsField.SPAN_DESCRIPTION]?.split('.').pop() || ''
  89. ) ||
  90. (uniqueSpanOps.size === 1 && spanMetrics[SPAN_OP] === ResourceSpanOps.IMAGE);
  91. const crumbs = useModuleBreadcrumbs('resource');
  92. return (
  93. <React.Fragment>
  94. {!isInDomainView && (
  95. <Layout.Header>
  96. <Layout.HeaderContent>
  97. <Breadcrumbs
  98. crumbs={[
  99. ...crumbs,
  100. {
  101. label: tct('[dataType] Summary', {dataType: DATA_TYPE}),
  102. },
  103. ]}
  104. />
  105. <Layout.Title>{spanMetrics[SpanMetricsField.SPAN_DESCRIPTION]}</Layout.Title>
  106. </Layout.HeaderContent>
  107. <Layout.HeaderActions>
  108. <ButtonBar gap={1}>
  109. <FeedbackWidgetButton />
  110. </ButtonBar>
  111. </Layout.HeaderActions>
  112. </Layout.Header>
  113. )}
  114. {isInDomainView && (
  115. <FrontendHeader
  116. headerTitle={spanMetrics[SpanMetricsField.SPAN_DESCRIPTION]}
  117. breadcrumbs={[
  118. {
  119. label: tct('[dataType] Summary', {dataType: DATA_TYPE}),
  120. },
  121. ]}
  122. module={ModuleName.RESOURCE}
  123. />
  124. )}
  125. <ModuleBodyUpsellHook moduleName={ModuleName.RESOURCE}>
  126. <Layout.Body>
  127. <Layout.Main fullWidth>
  128. <ModuleLayout.Layout>
  129. <ModuleLayout.Full>
  130. <HeaderContainer>
  131. <ToolRibbon>
  132. <PageFilterBar condensed>
  133. <ProjectPageFilter />
  134. <EnvironmentPageFilter />
  135. <DatePageFilter />
  136. </PageFilterBar>
  137. <RenderBlockingSelector
  138. value={filters[RESOURCE_RENDER_BLOCKING_STATUS] || ''}
  139. />
  140. <SubregionSelector />
  141. </ToolRibbon>
  142. <ResourceInfo
  143. isLoading={isPending}
  144. avgContentLength={spanMetrics[`avg(${HTTP_RESPONSE_CONTENT_LENGTH})`]}
  145. avgDecodedContentLength={
  146. spanMetrics[`avg(${HTTP_DECODED_RESPONSE_CONTENT_LENGTH})`]
  147. }
  148. avgTransferSize={spanMetrics[`avg(${HTTP_RESPONSE_TRANSFER_SIZE})`]}
  149. avgDuration={spanMetrics[`avg(${SPAN_SELF_TIME})`]}
  150. throughput={spanMetrics['spm()']}
  151. timeSpentTotal={spanMetrics[`sum(${SPAN_SELF_TIME})`]}
  152. timeSpentPercentage={spanMetrics[`time_spent_percentage()`]}
  153. />
  154. </HeaderContainer>
  155. </ModuleLayout.Full>
  156. {isImage && (
  157. <ModuleLayout.Full>
  158. <SampleImages groupId={groupId} projectId={data?.[0]?.['project.id']} />
  159. </ModuleLayout.Full>
  160. )}
  161. <ResourceSummaryCharts groupId={groupId} />
  162. <ModuleLayout.Full>
  163. <ResourceSummaryTable />
  164. </ModuleLayout.Full>
  165. <ModuleLayout.Full>
  166. <SampleList
  167. transactionRoute={webVitalsModuleURL}
  168. subregions={filters[SpanMetricsField.USER_GEO_SUBREGION]}
  169. groupId={groupId}
  170. moduleName={ModuleName.RESOURCE}
  171. transactionName={transaction as string}
  172. referrer={TraceViewSources.ASSETS_MODULE}
  173. />
  174. </ModuleLayout.Full>
  175. </ModuleLayout.Layout>
  176. </Layout.Main>
  177. </Layout.Body>
  178. </ModuleBodyUpsellHook>
  179. </React.Fragment>
  180. );
  181. }
  182. function PageWithProviders() {
  183. return (
  184. <ModulePageProviders
  185. moduleName="resource"
  186. pageTitle={`${DATA_TYPE} ${t('Summary')}`}
  187. features="insights-initial-modules"
  188. >
  189. <ResourceSummary />
  190. </ModulePageProviders>
  191. );
  192. }
  193. export default PageWithProviders;