traceMetadataHeader.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. import {useCallback} from 'react';
  2. import type {Location} from 'history';
  3. import omit from 'lodash/omit';
  4. import type {Crumb} from 'sentry/components/breadcrumbs';
  5. import Breadcrumbs from 'sentry/components/breadcrumbs';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import DiscoverButton from 'sentry/components/discoverButton';
  8. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  9. import * as Layout from 'sentry/components/layouts/thirds';
  10. import {t} from 'sentry/locale';
  11. import type {EventTransaction} from 'sentry/types/event';
  12. import type {Organization} from 'sentry/types/organization';
  13. import {trackAnalytics} from 'sentry/utils/analytics';
  14. import type EventView from 'sentry/utils/discover/eventView';
  15. import {SavedQueryDatasets} from 'sentry/utils/discover/types';
  16. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  17. import type RequestError from 'sentry/utils/requestError/requestError';
  18. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  19. import {useLocation} from 'sentry/utils/useLocation';
  20. import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
  21. import TraceConfigurations from 'sentry/views/performance/newTraceDetails/traceConfigurations';
  22. import Tab from '../transactionSummary/tabs';
  23. interface TraceMetadataHeaderProps {
  24. organization: Organization;
  25. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  26. traceEventView: EventView;
  27. traceSlug: string;
  28. }
  29. export const enum TraceViewSources {
  30. TRACES = 'traces',
  31. METRICS = 'metrics',
  32. DISCOVER = 'discover',
  33. REQUESTS_MODULE = 'requests_module',
  34. QUERIES_MODULE = 'queries_module',
  35. ASSETS_MODULE = 'assets_module',
  36. APP_STARTS_MODULE = 'app_starts_module',
  37. SCREEN_LOADS_MODULE = 'screen_loads_module',
  38. WEB_VITALS_MODULE = 'web_vitals_module',
  39. CACHES_MODULE = 'caches_module',
  40. QUEUES_MODULE = 'queues_module',
  41. PERFORMANCE_TRANSACTION_SUMMARY = 'performance_transaction_summary',
  42. PERFORMANCE_TRANSACTION_SUMMARY_PROFILES = 'performance_transaction_summary_profiles',
  43. ISSUE_DETAILS = 'issue_details',
  44. }
  45. function getBreadCrumbTarget(
  46. path: string,
  47. query: Location['query'],
  48. organization: Organization
  49. ) {
  50. return {
  51. pathname: normalizeUrl(`/organizations/${organization.slug}/${path}`),
  52. // Remove traceView specific query parameters that are not needed when navigating back.
  53. query: {...omit(query, ['node', 'fov', 'timestamp', 'eventId'])},
  54. };
  55. }
  56. function getPerformanceBreadCrumbs(organization: Organization, location: Location) {
  57. const crumbs: Crumb[] = [];
  58. crumbs.push({
  59. label: t('Performance'),
  60. to: getBreadCrumbTarget(`performance`, location.query, organization),
  61. });
  62. switch (location.query.tab) {
  63. case Tab.EVENTS:
  64. crumbs.push({
  65. label: t('All Events'),
  66. to: getBreadCrumbTarget(
  67. `performance/summary/events`,
  68. location.query,
  69. organization
  70. ),
  71. });
  72. break;
  73. case Tab.TAGS:
  74. crumbs.push({
  75. label: t('Tags'),
  76. to: getBreadCrumbTarget(`performance/summary/tags`, location.query, organization),
  77. });
  78. break;
  79. case Tab.SPANS:
  80. crumbs.push({
  81. label: t('Spans'),
  82. to: getBreadCrumbTarget(
  83. `performance/summary/spans`,
  84. location.query,
  85. organization
  86. ),
  87. });
  88. const {spanSlug} = location.query;
  89. if (spanSlug) {
  90. crumbs.push({
  91. label: t('Span Summary'),
  92. to: getBreadCrumbTarget(
  93. `performance/summary/spans/${spanSlug}`,
  94. location.query,
  95. organization
  96. ),
  97. });
  98. }
  99. break;
  100. case Tab.AGGREGATE_WATERFALL:
  101. crumbs.push({
  102. label: t('Transaction Summary'),
  103. to: getBreadCrumbTarget(
  104. `performance/summary/aggregateWaterfall`,
  105. location.query,
  106. organization
  107. ),
  108. });
  109. break;
  110. default:
  111. crumbs.push({
  112. label: t('Transaction Summary'),
  113. to: getBreadCrumbTarget(`performance/summary`, location.query, organization),
  114. });
  115. break;
  116. }
  117. crumbs.push({
  118. label: t('Trace View'),
  119. });
  120. return crumbs;
  121. }
  122. function getIssuesBreadCrumbs(organization: Organization, location: Location) {
  123. const crumbs: Crumb[] = [];
  124. crumbs.push({
  125. label: t('Issues'),
  126. to: getBreadCrumbTarget(`issues`, location.query, organization),
  127. });
  128. if (location.query.groupId) {
  129. crumbs.push({
  130. label: t('Issue Details'),
  131. to: getBreadCrumbTarget(
  132. `issues/${location.query.groupId}`,
  133. location.query,
  134. organization
  135. ),
  136. });
  137. }
  138. crumbs.push({
  139. label: t('Trace View'),
  140. });
  141. return crumbs;
  142. }
  143. function getInsightsModuleBreadcrumbs(location: Location, organization: Organization) {
  144. const crumbs: Crumb[] = [];
  145. crumbs.push({
  146. label: t('Insights'),
  147. });
  148. switch (location.query.source) {
  149. case TraceViewSources.REQUESTS_MODULE:
  150. crumbs.push({
  151. label: t('Requests'),
  152. to: getBreadCrumbTarget(`insights/http/`, location.query, organization),
  153. });
  154. crumbs.push({
  155. label: t('Domain Summary'),
  156. to: getBreadCrumbTarget(`insights/http/domains/`, location.query, organization),
  157. });
  158. break;
  159. case TraceViewSources.QUERIES_MODULE:
  160. crumbs.push({
  161. label: t('Queries'),
  162. to: getBreadCrumbTarget(`insights/database`, location.query, organization),
  163. });
  164. if (location.query.groupId) {
  165. crumbs.push({
  166. label: t('Query Summary'),
  167. to: getBreadCrumbTarget(
  168. `insights/database/spans/span/${location.query.groupId}`,
  169. location.query,
  170. organization
  171. ),
  172. });
  173. } else {
  174. crumbs.push({
  175. label: t('Query Summary'),
  176. });
  177. }
  178. break;
  179. case TraceViewSources.ASSETS_MODULE:
  180. crumbs.push({
  181. label: t('Assets'),
  182. to: getBreadCrumbTarget(`insights/browser/assets`, location.query, organization),
  183. });
  184. if (location.query.groupId) {
  185. crumbs.push({
  186. label: t('Asset Summary'),
  187. to: getBreadCrumbTarget(
  188. `insights/browser/assets/spans/span/${location.query.groupId}`,
  189. location.query,
  190. organization
  191. ),
  192. });
  193. } else {
  194. crumbs.push({
  195. label: t('Asset Summary'),
  196. });
  197. }
  198. break;
  199. case TraceViewSources.APP_STARTS_MODULE:
  200. crumbs.push({
  201. label: t('App Starts'),
  202. to: getBreadCrumbTarget(
  203. `insights/mobile/app-startup`,
  204. location.query,
  205. organization
  206. ),
  207. });
  208. crumbs.push({
  209. label: t('Screen Summary'),
  210. to: getBreadCrumbTarget(
  211. `mobile/app-startup/spans/`,
  212. location.query,
  213. organization
  214. ),
  215. });
  216. break;
  217. case TraceViewSources.SCREEN_LOADS_MODULE:
  218. crumbs.push({
  219. label: t('Screen Loads'),
  220. to: getBreadCrumbTarget(`insights/mobile/screens`, location.query, organization),
  221. });
  222. crumbs.push({
  223. label: t('Screen Summary'),
  224. to: getBreadCrumbTarget(
  225. `insights/mobile/screens/spans`,
  226. location.query,
  227. organization
  228. ),
  229. });
  230. break;
  231. case TraceViewSources.WEB_VITALS_MODULE:
  232. crumbs.push({
  233. label: t('Web Vitals'),
  234. to: getBreadCrumbTarget(
  235. `insights/browser/pageloads`,
  236. location.query,
  237. organization
  238. ),
  239. });
  240. crumbs.push({
  241. label: t('Page Overview'),
  242. to: getBreadCrumbTarget(
  243. `insights/browser/pageloads/overview`,
  244. location.query,
  245. organization
  246. ),
  247. });
  248. break;
  249. case TraceViewSources.CACHES_MODULE:
  250. crumbs.push({
  251. label: t('Caches'),
  252. to: getBreadCrumbTarget(`insights/caches`, location.query, organization),
  253. });
  254. break;
  255. case TraceViewSources.QUEUES_MODULE:
  256. crumbs.push({
  257. label: t('Queues'),
  258. to: getBreadCrumbTarget(`insights/queues`, location.query, organization),
  259. });
  260. crumbs.push({
  261. label: t('Destination Summary'),
  262. to: getBreadCrumbTarget(
  263. `insights/queues/destination`,
  264. location.query,
  265. organization
  266. ),
  267. });
  268. break;
  269. default:
  270. break;
  271. }
  272. crumbs.push({
  273. label: t('Trace View'),
  274. });
  275. return crumbs;
  276. }
  277. function getTraceViewBreadcrumbs(
  278. organization: Organization,
  279. location: Location
  280. ): Crumb[] {
  281. switch (location.query.source) {
  282. case TraceViewSources.TRACES:
  283. return [
  284. {
  285. label: t('Traces'),
  286. to: getBreadCrumbTarget(`traces`, location.query, organization),
  287. },
  288. {
  289. label: t('Trace View'),
  290. },
  291. ];
  292. case TraceViewSources.DISCOVER:
  293. return [
  294. {
  295. label: t('Discover'),
  296. to: getBreadCrumbTarget(`discover/homepage`, location.query, organization),
  297. },
  298. {
  299. label: t('Trace View'),
  300. },
  301. ];
  302. case TraceViewSources.METRICS:
  303. return [
  304. {
  305. label: t('Metrics'),
  306. to: getBreadCrumbTarget(`metrics`, location.query, organization),
  307. },
  308. {
  309. label: t('Trace View'),
  310. },
  311. ];
  312. case TraceViewSources.ISSUE_DETAILS:
  313. return getIssuesBreadCrumbs(organization, location);
  314. case TraceViewSources.PERFORMANCE_TRANSACTION_SUMMARY:
  315. return getPerformanceBreadCrumbs(organization, location);
  316. case TraceViewSources.REQUESTS_MODULE:
  317. case TraceViewSources.QUERIES_MODULE:
  318. case TraceViewSources.ASSETS_MODULE:
  319. case TraceViewSources.APP_STARTS_MODULE:
  320. case TraceViewSources.SCREEN_LOADS_MODULE:
  321. case TraceViewSources.WEB_VITALS_MODULE:
  322. case TraceViewSources.CACHES_MODULE:
  323. case TraceViewSources.QUEUES_MODULE:
  324. return getInsightsModuleBreadcrumbs(location, organization);
  325. default:
  326. return [{label: t('Trace View')}];
  327. }
  328. }
  329. export function TraceMetadataHeader(props: TraceMetadataHeaderProps) {
  330. const location = useLocation();
  331. const trackOpenInDiscover = useCallback(() => {
  332. trackAnalytics('performance_views.trace_view.open_in_discover', {
  333. organization: props.organization,
  334. });
  335. }, [props.organization]);
  336. return (
  337. <Layout.Header>
  338. <Layout.HeaderContent>
  339. <Breadcrumbs crumbs={getTraceViewBreadcrumbs(props.organization, location)} />
  340. </Layout.HeaderContent>
  341. <Layout.HeaderActions>
  342. <ButtonBar gap={1}>
  343. <TraceConfigurations rootEventResults={props.rootEventResults} />
  344. <DiscoverButton
  345. size="sm"
  346. to={props.traceEventView.getResultsViewUrlTarget(
  347. props.organization.slug,
  348. false,
  349. hasDatasetSelector(props.organization)
  350. ? SavedQueryDatasets.ERRORS
  351. : undefined
  352. )}
  353. onClick={trackOpenInDiscover}
  354. >
  355. {t('Open in Discover')}
  356. </DiscoverButton>
  357. <FeedbackWidgetButton />
  358. </ButtonBar>
  359. </Layout.HeaderActions>
  360. </Layout.Header>
  361. );
  362. }