index.tsx 6.9 KB

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