content.tsx 8.1 KB

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