title.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import {Fragment, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
  4. import ExternalLink from 'sentry/components/links/externalLink';
  5. import {Tooltip} from 'sentry/components/tooltip';
  6. import {t, tct} from 'sentry/locale';
  7. import {isRootTransaction} from '../../traceDetails/utils';
  8. import {isTraceNode} from '../traceGuards';
  9. import type {TraceTree} from '../traceModels/traceTree';
  10. const CANDIDATE_TRACE_TITLE_OPS = ['pageload', 'navigation'];
  11. type TraceTitle = {
  12. op: string;
  13. transaction?: string;
  14. } | null;
  15. interface TitleProps {
  16. traceSlug: string;
  17. tree: TraceTree;
  18. }
  19. export function Title({traceSlug, tree}: TitleProps) {
  20. const traceTitle: TraceTitle = useMemo(() => {
  21. const trace = tree.root.children[0];
  22. if (!trace) {
  23. return null;
  24. }
  25. if (!isTraceNode(trace)) {
  26. throw new TypeError('Not trace node');
  27. }
  28. let firstRootTransaction: TraceTitle = null;
  29. let candidateTransaction: TraceTitle = null;
  30. let firstTransaction: TraceTitle = null;
  31. for (const transaction of trace.value.transactions || []) {
  32. const title = {
  33. op: transaction['transaction.op'],
  34. transaction: transaction.transaction,
  35. };
  36. // If we find a root transaction, we can stop looking and use it for the title.
  37. if (!firstRootTransaction && isRootTransaction(transaction)) {
  38. firstRootTransaction = title;
  39. break;
  40. } else if (
  41. // If we haven't found a root transaction, but we found a candidate transaction
  42. // with an op that we care about, we can use it for the title. We keep looking for
  43. // a root.
  44. !candidateTransaction &&
  45. CANDIDATE_TRACE_TITLE_OPS.includes(transaction['transaction.op'])
  46. ) {
  47. candidateTransaction = title;
  48. continue;
  49. } else if (!firstTransaction) {
  50. // If we haven't found a root or candidate transaction, we can use the first transaction
  51. // in the trace for the title.
  52. firstTransaction = title;
  53. }
  54. }
  55. return firstRootTransaction ?? candidateTransaction ?? firstTransaction;
  56. }, [tree.root.children]);
  57. return (
  58. <div>
  59. <HeaderTitle>
  60. {traceTitle ? (
  61. traceTitle.transaction ? (
  62. <Fragment>
  63. <strong>{traceTitle.op} - </strong>
  64. {traceTitle.transaction}
  65. </Fragment>
  66. ) : (
  67. '\u2014'
  68. )
  69. ) : (
  70. <Tooltip
  71. title={tct(
  72. 'Might be due to sampling, ad blockers, permissions or more.[break][link:Read the docs]',
  73. {
  74. break: <br />,
  75. link: (
  76. <ExternalLink href="https://docs.sentry.io/concepts/key-terms/tracing/trace-view/#troubleshooting" />
  77. ),
  78. }
  79. )}
  80. showUnderline
  81. position="right"
  82. isHoverable
  83. >
  84. <strong>{t('Missing Trace Root')}</strong>
  85. </Tooltip>
  86. )}
  87. </HeaderTitle>
  88. <HeaderSubtitle>
  89. Trace ID: {traceSlug}
  90. <CopyToClipboardButton borderless size="zero" iconSize="xs" text={traceSlug} />
  91. </HeaderSubtitle>
  92. </div>
  93. );
  94. }
  95. const HeaderTitle = styled('div')`
  96. font-size: ${p => p.theme.fontSizeExtraLarge};
  97. ${p => p.theme.overflowEllipsis};
  98. `;
  99. const HeaderSubtitle = styled('div')`
  100. font-size: ${p => p.theme.fontSizeMedium};
  101. color: ${p => p.theme.subText};
  102. `;