traceMetadataHeader.tsx 11 KB

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