content.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import {Fragment} from 'react';
  2. import type {Location} from 'history';
  3. import IdBadge from 'sentry/components/idBadge';
  4. import * as Layout from 'sentry/components/layouts/thirds';
  5. import type {Organization} from 'sentry/types/organization';
  6. import type {Project} from 'sentry/types/project';
  7. import type {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
  8. import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
  9. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  10. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import {useParams} from 'sentry/utils/useParams';
  13. import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover';
  14. import type {
  15. SpanMetricsQueryFilters,
  16. SpanMetricsResponse,
  17. } from 'sentry/views/insights/types';
  18. import Breadcrumb from 'sentry/views/performance/breadcrumb';
  19. import {SpanSummaryReferrer} from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/referrers';
  20. import SpanSummaryCharts from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/spanSummaryCharts';
  21. import SpanSummaryTable from 'sentry/views/performance/transactionSummary/transactionSpans/spanSummary/spanSummaryTable';
  22. import {getSelectedProjectPlatforms} from 'sentry/views/performance/utils';
  23. import Tab from '../../tabs';
  24. import SpanSummaryControls from './spanSummaryControls';
  25. import SpanSummaryHeader from './spanSummaryHeader';
  26. type Props = {
  27. organization: Organization;
  28. project: Project | undefined;
  29. spanSlug: SpanSlug;
  30. transactionName: string;
  31. };
  32. export default function SpanSummary(props: Props) {
  33. const {organization, project, transactionName, spanSlug} = props;
  34. const location = useLocation();
  35. // customize the route analytics event we send
  36. useRouteAnalyticsEventNames(
  37. 'performance_views.span_summary.view',
  38. 'Performance Views: Span Summary page viewed'
  39. );
  40. useRouteAnalyticsParams({
  41. project_platforms: project ? getSelectedProjectPlatforms(location, [project]) : '',
  42. });
  43. return (
  44. <Fragment>
  45. <Layout.Header>
  46. <Layout.HeaderContent>
  47. <Breadcrumb
  48. organization={organization}
  49. location={location}
  50. transaction={{
  51. project: project?.id ?? '',
  52. name: transactionName,
  53. }}
  54. tab={Tab.SPANS}
  55. spanSlug={spanSlug}
  56. />
  57. <Layout.Title>
  58. {project && (
  59. <IdBadge
  60. project={project}
  61. avatarSize={28}
  62. hideName
  63. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  64. />
  65. )}
  66. {transactionName}
  67. </Layout.Title>
  68. </Layout.HeaderContent>
  69. </Layout.Header>
  70. <Layout.Body>
  71. <Layout.Main fullWidth>
  72. <SpanSummaryContent
  73. location={location}
  74. organization={organization}
  75. project={project}
  76. transactionName={transactionName}
  77. />
  78. </Layout.Main>
  79. </Layout.Body>
  80. </Fragment>
  81. );
  82. }
  83. type ContentProps = {
  84. location: Location;
  85. organization: Organization;
  86. project: Project | undefined;
  87. transactionName: string;
  88. };
  89. function SpanSummaryContent(props: ContentProps) {
  90. const {transactionName, project} = props;
  91. const {spanSlug: spanParam} = useParams();
  92. const [spanOp, groupId] = spanParam.split(':');
  93. const filters: SpanMetricsQueryFilters = {
  94. 'span.group': groupId,
  95. 'span.op': spanOp,
  96. transaction: transactionName,
  97. };
  98. const {data: spanHeaderData} = useSpanMetrics(
  99. {
  100. search: MutableSearch.fromQueryObject(filters),
  101. fields: ['span.description', 'sum(span.duration)', 'count()'],
  102. sorts: [{field: 'sum(span.duration)', kind: 'desc'}],
  103. },
  104. SpanSummaryReferrer.SPAN_SUMMARY_HEADER_DATA
  105. );
  106. // Average span duration must be queried for separately, since it could get broken up into multiple groups if used in the first query
  107. const {data: avgDurationData} = useSpanMetrics(
  108. {
  109. search: MutableSearch.fromQueryObject(filters),
  110. fields: ['avg(span.duration)'],
  111. },
  112. SpanSummaryReferrer.SPAN_SUMMARY_HEADER_DATA
  113. );
  114. const parsedData = parseSpanHeaderData(spanHeaderData);
  115. return (
  116. <Fragment>
  117. <SpanSummaryControls />
  118. <SpanSummaryHeader
  119. spanOp={spanOp}
  120. spanDescription={parsedData?.description}
  121. avgDuration={avgDurationData[0]?.['avg(span.duration)']}
  122. timeSpent={parsedData?.timeSpent}
  123. spanCount={parsedData?.spanCount}
  124. />
  125. <SpanSummaryCharts />
  126. <SpanSummaryTable project={project} />
  127. </Fragment>
  128. );
  129. }
  130. function parseSpanHeaderData(data: Partial<SpanMetricsResponse>[]) {
  131. if (!data || data.length === 0) {
  132. return undefined;
  133. }
  134. if (data.length === 1) {
  135. return {
  136. description: data[0]?.['span.description'],
  137. timeSpent: data[0]?.['sum(span.duration)'],
  138. spanCount: data[0]?.['count()'],
  139. };
  140. }
  141. const cumulativeData = {
  142. description: undefined,
  143. timeSpent: 0,
  144. spanCount: 0,
  145. };
  146. data.forEach(datum => {
  147. cumulativeData.timeSpent += datum['sum(span.self_time)'] ?? 0;
  148. cumulativeData.spanCount += datum['count()'] ?? 0;
  149. });
  150. return cumulativeData;
  151. }