traceMetadataHeader.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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. crumbs.push({
  140. label: t('Insights'),
  141. });
  142. switch (location.query.referrer) {
  143. case TraceViewSources.REQUESTS_MODULE:
  144. crumbs.push({
  145. label: t('Requests'),
  146. to: getBreadCrumbTarget(`insights/http/`, location.query, organization),
  147. });
  148. crumbs.push({
  149. label: t('Domain Summary'),
  150. to: getBreadCrumbTarget(`insights/http/domains/`, location.query, organization),
  151. });
  152. break;
  153. case TraceViewSources.QUERIES_MODULE:
  154. crumbs.push({
  155. label: t('Queries'),
  156. to: getBreadCrumbTarget(`insights/database`, location.query, organization),
  157. });
  158. if (location.query.groupId) {
  159. crumbs.push({
  160. label: t('Query Summary'),
  161. to: getBreadCrumbTarget(
  162. `insights/database/spans/span/${location.query.groupId}`,
  163. location.query,
  164. organization
  165. ),
  166. });
  167. } else {
  168. crumbs.push({
  169. label: t('Query Summary'),
  170. });
  171. }
  172. break;
  173. case TraceViewSources.ASSETS_MODULE:
  174. crumbs.push({
  175. label: t('Assets'),
  176. to: getBreadCrumbTarget(`insights/browser/assets`, location.query, organization),
  177. });
  178. if (location.query.groupId) {
  179. crumbs.push({
  180. label: t('Asset Summary'),
  181. to: getBreadCrumbTarget(
  182. `insights/browser/assets/spans/span/${location.query.groupId}`,
  183. location.query,
  184. organization
  185. ),
  186. });
  187. } else {
  188. crumbs.push({
  189. label: t('Asset Summary'),
  190. });
  191. }
  192. break;
  193. case TraceViewSources.APP_STARTS_MODULE:
  194. crumbs.push({
  195. label: t('App Starts'),
  196. to: getBreadCrumbTarget(
  197. `insights/mobile/app-startup`,
  198. location.query,
  199. organization
  200. ),
  201. });
  202. crumbs.push({
  203. label: t('Screen Summary'),
  204. to: getBreadCrumbTarget(
  205. `mobile/app-startup/spans/`,
  206. location.query,
  207. organization
  208. ),
  209. });
  210. break;
  211. case TraceViewSources.SCREEN_LOADS_MODULE:
  212. crumbs.push({
  213. label: t('Screen Loads'),
  214. to: getBreadCrumbTarget(`insights/mobile/screens`, location.query, organization),
  215. });
  216. crumbs.push({
  217. label: t('Screen Summary'),
  218. to: getBreadCrumbTarget(
  219. `insights/mobile/screens/spans`,
  220. location.query,
  221. organization
  222. ),
  223. });
  224. break;
  225. case TraceViewSources.WEB_VITALS_MODULE:
  226. crumbs.push({
  227. label: t('Web Vitals'),
  228. to: getBreadCrumbTarget(
  229. `insights/browser/pageloads`,
  230. location.query,
  231. organization
  232. ),
  233. });
  234. crumbs.push({
  235. label: t('Page Overview'),
  236. to: getBreadCrumbTarget(
  237. `insights/browser/pageloads/overview`,
  238. location.query,
  239. organization
  240. ),
  241. });
  242. break;
  243. case TraceViewSources.CACHES_MODULE:
  244. crumbs.push({
  245. label: t('Caches'),
  246. to: getBreadCrumbTarget(`insights/caches`, location.query, organization),
  247. });
  248. break;
  249. case TraceViewSources.QUEUES_MODULE:
  250. crumbs.push({
  251. label: t('Queues'),
  252. to: getBreadCrumbTarget(`insights/queues`, location.query, organization),
  253. });
  254. crumbs.push({
  255. label: t('Destination Summary'),
  256. to: getBreadCrumbTarget(
  257. `insights/queues/destination`,
  258. location.query,
  259. organization
  260. ),
  261. });
  262. break;
  263. default:
  264. break;
  265. }
  266. crumbs.push({
  267. label: t('Trace View'),
  268. });
  269. return crumbs;
  270. }
  271. function getTraceViewBreadcrumbs(
  272. organization: Organization,
  273. location: Location
  274. ): Crumb[] {
  275. switch (location.query.referrer) {
  276. case TraceViewSources.TRACES:
  277. return [
  278. {
  279. label: t('Traces'),
  280. to: getBreadCrumbTarget(`traces`, location.query, organization),
  281. },
  282. {
  283. label: t('Trace View'),
  284. },
  285. ];
  286. case TraceViewSources.DISCOVER:
  287. return [
  288. {
  289. label: t('Discover'),
  290. to: getBreadCrumbTarget(`discover/homepage`, location.query, organization),
  291. },
  292. {
  293. label: t('Trace View'),
  294. },
  295. ];
  296. case TraceViewSources.METRICS:
  297. return [
  298. {
  299. label: t('Metrics'),
  300. to: getBreadCrumbTarget(`metrics`, location.query, organization),
  301. },
  302. {
  303. label: t('Trace View'),
  304. },
  305. ];
  306. case TraceViewSources.ISSUE_DETAILS:
  307. return getIssuesBreadCrumbs(organization, location);
  308. case TraceViewSources.PERFORMANCE_TRANSACTION_SUMMARY:
  309. return getPerformanceBreadCrumbs(organization, location);
  310. case TraceViewSources.REQUESTS_MODULE:
  311. case TraceViewSources.QUERIES_MODULE:
  312. case TraceViewSources.ASSETS_MODULE:
  313. case TraceViewSources.APP_STARTS_MODULE:
  314. case TraceViewSources.SCREEN_LOADS_MODULE:
  315. case TraceViewSources.WEB_VITALS_MODULE:
  316. case TraceViewSources.CACHES_MODULE:
  317. case TraceViewSources.QUEUES_MODULE:
  318. return getInsightsModuleBreadcrumbs(location, organization);
  319. default:
  320. return [{label: t('Trace View')}];
  321. }
  322. }
  323. export function TraceMetadataHeader(props: TraceMetadataHeaderProps) {
  324. const location = useLocation();
  325. const trackOpenInDiscover = useCallback(() => {
  326. trackAnalytics('performance_views.trace_view.open_in_discover', {
  327. organization: props.organization,
  328. });
  329. }, [props.organization]);
  330. return (
  331. <Layout.Header>
  332. <Layout.HeaderContent>
  333. <Breadcrumbs crumbs={getTraceViewBreadcrumbs(props.organization, location)} />
  334. </Layout.HeaderContent>
  335. <Layout.HeaderActions>
  336. <ButtonBar gap={1}>
  337. <DiscoverButton
  338. size="sm"
  339. to={props.traceEventView.getResultsViewUrlTarget(props.organization.slug)}
  340. onClick={trackOpenInDiscover}
  341. >
  342. {t('Open in Discover')}
  343. </DiscoverButton>
  344. <FeedbackWidgetButton />
  345. </ButtonBar>
  346. </Layout.HeaderActions>
  347. </Layout.Header>
  348. );
  349. }