content.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import {Fragment} from 'react';
  2. import type {Location} from 'history';
  3. import Feature from 'sentry/components/acl/feature';
  4. import IdBadge from 'sentry/components/idBadge';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import type {Organization} from 'sentry/types/organization';
  7. import type {Project} from 'sentry/types/project';
  8. import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
  9. import type EventView from 'sentry/utils/discover/eventView';
  10. import type {ChildrenProps as SpanExamplesProps} from 'sentry/utils/performance/suspectSpans/spanExamplesQuery';
  11. import SpanExamplesQuery from 'sentry/utils/performance/suspectSpans/spanExamplesQuery';
  12. import type {ChildrenProps as SuspectSpansProps} from 'sentry/utils/performance/suspectSpans/suspectSpansQuery';
  13. import SuspectSpansQuery from 'sentry/utils/performance/suspectSpans/suspectSpansQuery';
  14. import type {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
  15. import {setGroupedEntityTag} from 'sentry/utils/performanceForSentry';
  16. import {decodeScalar} from 'sentry/utils/queryString';
  17. import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
  18. import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
  19. import Breadcrumb from 'sentry/views/performance/breadcrumb';
  20. import {getSelectedProjectPlatforms} from 'sentry/views/performance/utils';
  21. import Tab from '../../tabs';
  22. import {SpanSortOthers} from '../types';
  23. import {getTotalsView} from '../utils';
  24. import SpanChart from './chart';
  25. import SpanDetailsControls from './spanDetailsControls';
  26. import SpanDetailsHeader from './spanDetailsHeader';
  27. import SpanTable from './spanDetailsTable';
  28. import {ZoomKeys} from './utils';
  29. type Props = {
  30. eventView: EventView;
  31. location: Location;
  32. organization: Organization;
  33. project: Project | undefined;
  34. spanSlug: SpanSlug;
  35. transactionName: string;
  36. };
  37. export default function SpanDetailsContentWrapper(props: Props) {
  38. const {location, organization, eventView, project, transactionName, spanSlug} = props;
  39. const minExclusiveTime = decodeScalar(location.query[ZoomKeys.MIN]);
  40. const maxExclusiveTime = decodeScalar(location.query[ZoomKeys.MAX]);
  41. // customize the route analytics event we send
  42. useRouteAnalyticsEventNames(
  43. 'performance_views.span_summary.view',
  44. 'Performance Views: Span Summary page viewed'
  45. );
  46. useRouteAnalyticsParams({
  47. project_platforms: project ? getSelectedProjectPlatforms(location, [project]) : '',
  48. });
  49. return (
  50. <Fragment>
  51. <Layout.Header>
  52. <Layout.HeaderContent>
  53. <Breadcrumb
  54. organization={organization}
  55. location={location}
  56. transaction={{
  57. project: project?.id ?? '',
  58. name: transactionName,
  59. }}
  60. tab={Tab.SPANS}
  61. spanSlug={spanSlug}
  62. />
  63. <Layout.Title>
  64. {project && (
  65. <IdBadge
  66. project={project}
  67. avatarSize={28}
  68. hideName
  69. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  70. />
  71. )}
  72. {transactionName}
  73. </Layout.Title>
  74. </Layout.HeaderContent>
  75. </Layout.Header>
  76. <Layout.Body>
  77. <Layout.Main fullWidth>
  78. <DiscoverQuery
  79. eventView={getTotalsView(eventView)}
  80. orgSlug={organization.slug}
  81. location={location}
  82. referrer="api.performance.transaction-spans"
  83. cursor="0:0:1"
  84. noPagination
  85. >
  86. {({tableData}) => {
  87. const totalCount: number | null =
  88. (tableData?.data?.[0]?.['count()'] as number) ?? null;
  89. if (totalCount) {
  90. setGroupedEntityTag('spans.totalCount', 1000, totalCount);
  91. }
  92. return (
  93. <SuspectSpansQuery
  94. location={location}
  95. orgSlug={organization.slug}
  96. eventView={getSpansEventView(eventView)}
  97. perSuspect={0}
  98. spanOps={[spanSlug.op]}
  99. spanGroups={[spanSlug.group]}
  100. cursor="0:0:1"
  101. minExclusiveTime={minExclusiveTime}
  102. maxExclusiveTime={maxExclusiveTime}
  103. >
  104. {suspectSpansResults => (
  105. <SpanExamplesQuery
  106. location={location}
  107. orgSlug={organization.slug}
  108. eventView={eventView}
  109. spanOp={spanSlug.op}
  110. spanGroup={spanSlug.group}
  111. limit={10}
  112. minExclusiveTime={minExclusiveTime}
  113. maxExclusiveTime={maxExclusiveTime}
  114. >
  115. {spanExamplesResults => (
  116. <SpanDetailsContent
  117. location={location}
  118. organization={organization}
  119. project={project}
  120. eventView={eventView}
  121. spanSlug={spanSlug}
  122. transactionName={transactionName}
  123. totalCount={totalCount}
  124. suspectSpansResults={suspectSpansResults}
  125. spanExamplesResults={spanExamplesResults}
  126. />
  127. )}
  128. </SpanExamplesQuery>
  129. )}
  130. </SuspectSpansQuery>
  131. );
  132. }}
  133. </DiscoverQuery>
  134. </Layout.Main>
  135. </Layout.Body>
  136. </Fragment>
  137. );
  138. }
  139. type ContentProps = {
  140. eventView: EventView;
  141. location: Location;
  142. organization: Organization;
  143. project: Project | undefined;
  144. spanExamplesResults: SpanExamplesProps;
  145. spanSlug: SpanSlug;
  146. suspectSpansResults: SuspectSpansProps;
  147. totalCount: number;
  148. transactionName: string;
  149. };
  150. function SpanDetailsContent(props: ContentProps) {
  151. const {
  152. location,
  153. organization,
  154. project,
  155. eventView,
  156. spanSlug,
  157. transactionName,
  158. totalCount,
  159. suspectSpansResults,
  160. spanExamplesResults,
  161. } = props;
  162. // There should always be exactly 1 result
  163. const suspectSpan = suspectSpansResults.suspectSpans?.[0];
  164. const examples = spanExamplesResults.examples?.[0]?.examples;
  165. const transactionCountContainingSpan = suspectSpan?.frequency;
  166. return (
  167. <Fragment>
  168. <Feature features="performance-span-histogram-view">
  169. <SpanDetailsControls
  170. organization={organization}
  171. location={location}
  172. eventView={eventView}
  173. />
  174. </Feature>
  175. <SpanDetailsHeader
  176. spanSlug={spanSlug}
  177. totalCount={totalCount}
  178. suspectSpan={suspectSpan}
  179. />
  180. <SpanChart
  181. totalCount={transactionCountContainingSpan}
  182. organization={organization}
  183. eventView={eventView}
  184. spanSlug={spanSlug}
  185. />
  186. <SpanTable
  187. location={location}
  188. organization={organization}
  189. project={project}
  190. suspectSpan={suspectSpan}
  191. transactionName={transactionName}
  192. isLoading={spanExamplesResults.isLoading}
  193. examples={examples ?? []}
  194. pageLinks={spanExamplesResults.pageLinks}
  195. />
  196. </Fragment>
  197. );
  198. }
  199. function getSpansEventView(eventView: EventView): EventView {
  200. // TODO: There is a bug where if the sort is not avg occurrence,
  201. // then the avg occurrence will never be added to the fields
  202. eventView = eventView.withSorts([{field: SpanSortOthers.AVG_OCCURRENCE, kind: 'desc'}]);
  203. eventView.fields = [
  204. {field: 'count()'},
  205. {field: 'count_unique(id)'},
  206. {field: 'equation|count() / count_unique(id)'},
  207. {field: 'sumArray(spans_exclusive_time)'},
  208. {field: 'percentileArray(spans_exclusive_time, 0.50)'},
  209. {field: 'percentileArray(spans_exclusive_time, 0.75)'},
  210. {field: 'percentileArray(spans_exclusive_time, 0.95)'},
  211. {field: 'percentileArray(spans_exclusive_time, 0.99)'},
  212. ];
  213. return eventView;
  214. }