traceMetadataHeader.tsx 10 KB

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