index.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import {Fragment, useMemo, useState} from 'react';
  2. import type {Location} from 'history';
  3. import ButtonBar from 'sentry/components/buttonBar';
  4. import DiscoverButton from 'sentry/components/discoverButton';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import NoProjectMessage from 'sentry/components/noProjectMessage';
  7. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  8. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  9. import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
  10. import {t} from 'sentry/locale';
  11. import type {EventTransaction, Organization} from 'sentry/types';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import EventView from 'sentry/utils/discover/eventView';
  14. import {TraceFullDetailedQuery} from 'sentry/utils/performance/quickTrace/traceFullQuery';
  15. import TraceMetaQuery, {
  16. type TraceMetaQueryChildrenProps,
  17. } from 'sentry/utils/performance/quickTrace/traceMetaQuery';
  18. import type {
  19. TraceFullDetailed,
  20. TraceSplitResults,
  21. } from 'sentry/utils/performance/quickTrace/types';
  22. import {useApiQuery} from 'sentry/utils/queryClient';
  23. import {decodeScalar} from 'sentry/utils/queryString';
  24. import {useLocation} from 'sentry/utils/useLocation';
  25. import useOrganization from 'sentry/utils/useOrganization';
  26. import {useParams} from 'sentry/utils/useParams';
  27. import useProjects from 'sentry/utils/useProjects';
  28. import Breadcrumb from '../breadcrumb';
  29. import Trace from './trace';
  30. import {TraceFooter} from './traceFooter';
  31. import TraceHeader from './traceHeader';
  32. import {TraceTree} from './traceTree';
  33. import TraceWarnings from './traceWarnings';
  34. const DOCUMENT_TITLE = [t('Trace Details'), t('Performance')].join(' — ');
  35. export function TraceView() {
  36. const location = useLocation();
  37. const organization = useOrganization();
  38. const params = useParams<{traceSlug?: string}>();
  39. const traceSlug = params.traceSlug?.trim() ?? '';
  40. const dateSelection = useMemo(() => {
  41. const queryParams = normalizeDateTimeParams(location.query, {
  42. allowAbsolutePageDatetime: true,
  43. });
  44. const start = decodeScalar(queryParams.start);
  45. const end = decodeScalar(queryParams.end);
  46. const statsPeriod = decodeScalar(queryParams.statsPeriod);
  47. return {start, end, statsPeriod};
  48. }, [location.query]);
  49. const _traceEventView = useMemo(() => {
  50. const {start, end, statsPeriod} = dateSelection;
  51. return EventView.fromSavedQuery({
  52. id: undefined,
  53. name: `Events with Trace ID ${traceSlug}`,
  54. fields: ['title', 'event.type', 'project', 'timestamp'],
  55. orderby: '-timestamp',
  56. query: `trace:${traceSlug}`,
  57. projects: [ALL_ACCESS_PROJECTS],
  58. version: 2,
  59. start,
  60. end,
  61. range: statsPeriod,
  62. });
  63. }, [dateSelection, traceSlug]);
  64. const [_limit, _setLimit] = useState<number>();
  65. // const _handleLimithange = useCallback((newLimit: number) => {
  66. // setLimit(newLimit);
  67. // }, []);
  68. return (
  69. <SentryDocumentTitle title={DOCUMENT_TITLE} orgSlug={organization.slug}>
  70. <Layout.Page>
  71. <NoProjectMessage organization={organization}>
  72. <TraceFullDetailedQuery
  73. type="spans"
  74. location={location}
  75. orgSlug={organization.slug}
  76. traceId={traceSlug}
  77. start={dateSelection.start}
  78. end={dateSelection.end}
  79. statsPeriod={dateSelection.statsPeriod}
  80. >
  81. {trace => (
  82. <TraceMetaQuery
  83. location={location}
  84. orgSlug={organization.slug}
  85. traceId={traceSlug}
  86. start={dateSelection.start}
  87. end={dateSelection.end}
  88. statsPeriod={dateSelection.statsPeriod}
  89. >
  90. {metaResults => (
  91. <TraceViewContent
  92. traceSplitResult={trace?.traces}
  93. traceSlug={traceSlug}
  94. organization={organization}
  95. location={location}
  96. traceEventView={_traceEventView}
  97. metaResults={metaResults}
  98. />
  99. )}
  100. </TraceMetaQuery>
  101. )}
  102. </TraceFullDetailedQuery>
  103. </NoProjectMessage>
  104. </Layout.Page>
  105. </SentryDocumentTitle>
  106. );
  107. }
  108. type TraceViewContentProps = {
  109. location: Location;
  110. metaResults: TraceMetaQueryChildrenProps;
  111. organization: Organization;
  112. traceEventView: EventView;
  113. traceSlug: string;
  114. traceSplitResult: TraceSplitResults<TraceFullDetailed> | null;
  115. };
  116. function TraceViewContent(props: TraceViewContentProps) {
  117. const rootEvent = props.traceSplitResult?.transactions?.[0];
  118. const {projects} = useProjects();
  119. const tree = useMemo(() => {
  120. if (!props.traceSplitResult) {
  121. return TraceTree.Loading({
  122. project_slug: projects?.[0]?.slug ?? '',
  123. event_id: props.traceSlug,
  124. });
  125. }
  126. return TraceTree.FromTrace(props.traceSplitResult);
  127. }, [props.traceSlug, props.traceSplitResult, projects]);
  128. const traceType = useMemo(() => {
  129. return TraceTree.GetTraceType(tree.root);
  130. }, [tree]);
  131. const rootEventResults = useApiQuery<EventTransaction>(
  132. [
  133. `/organizations/${props.organization.slug}/events/${rootEvent?.project_slug}:${rootEvent?.event_id}/`,
  134. {
  135. query: {
  136. referrer: 'trace-details-summary',
  137. },
  138. },
  139. ],
  140. {
  141. staleTime: 0,
  142. enabled: !!(
  143. props.traceSplitResult?.transactions &&
  144. props.traceSplitResult.transactions.length > 0
  145. ),
  146. }
  147. );
  148. return (
  149. <Fragment>
  150. <Layout.Header>
  151. <Layout.HeaderContent>
  152. <Breadcrumb
  153. organization={props.organization}
  154. location={props.location}
  155. transaction={{
  156. project: rootEventResults.data?.projectID ?? '',
  157. name: rootEventResults.data?.title ?? '',
  158. }}
  159. traceSlug={props.traceSlug}
  160. />
  161. <Layout.Title data-test-id="trace-header">
  162. {t('Trace ID: %s', props.traceSlug)}
  163. </Layout.Title>
  164. </Layout.HeaderContent>
  165. <Layout.HeaderActions>
  166. <ButtonBar gap={1}>
  167. <DiscoverButton
  168. size="sm"
  169. to={props.traceEventView.getResultsViewUrlTarget(props.organization.slug)}
  170. onClick={() => {
  171. trackAnalytics('performance_views.trace_view.open_in_discover', {
  172. organization: props.organization,
  173. });
  174. }}
  175. >
  176. {t('Open in Discover')}
  177. </DiscoverButton>
  178. </ButtonBar>
  179. </Layout.HeaderActions>
  180. </Layout.Header>
  181. <Layout.Body>
  182. <Layout.Main fullWidth>
  183. {traceType ? <TraceWarnings type={traceType} /> : null}
  184. <TraceHeader
  185. rootEventResults={rootEventResults}
  186. metaResults={props.metaResults}
  187. organization={props.organization}
  188. traces={props.traceSplitResult}
  189. />
  190. <Trace trace={tree} trace_id={props.traceSlug} />
  191. <TraceFooter
  192. rootEventResults={rootEventResults}
  193. organization={props.organization}
  194. location={props.location}
  195. traces={props.traceSplitResult}
  196. traceEventView={props.traceEventView}
  197. />
  198. </Layout.Main>
  199. </Layout.Body>
  200. </Fragment>
  201. );
  202. }