index.tsx 6.9 KB

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