traceMetadataHeader.tsx 10 KB

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