header.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import {useCallback} from 'react';
  2. import {Location} from 'history';
  3. import Feature from 'sentry/components/acl/feature';
  4. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  5. import ButtonBar from 'sentry/components/buttonBar';
  6. import {CreateAlertFromViewButton} from 'sentry/components/createAlertButton';
  7. import FeatureBadge from 'sentry/components/featureBadge';
  8. import IdBadge from 'sentry/components/idBadge';
  9. import * as Layout from 'sentry/components/layouts/thirds';
  10. import {isProfilingSupportedOrProjectHasProfiles} from 'sentry/components/profiling/ProfilingOnboarding/util';
  11. import ReplayCountBadge from 'sentry/components/replays/replayCountBadge';
  12. import ReplaysFeatureBadge from 'sentry/components/replays/replaysFeatureBadge';
  13. import useReplaysCount from 'sentry/components/replays/useReplaysCount';
  14. import {Item, TabList} from 'sentry/components/tabs';
  15. import {t} from 'sentry/locale';
  16. import {Organization, Project} from 'sentry/types';
  17. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  18. import EventView from 'sentry/utils/discover/eventView';
  19. import {MetricsCardinalityContext} from 'sentry/utils/performance/contexts/metricsCardinality';
  20. import HasMeasurementsQuery from 'sentry/utils/performance/vitals/hasMeasurementsQuery';
  21. import projectSupportsReplay from 'sentry/utils/replays/projectSupportsReplay';
  22. import Breadcrumb from 'sentry/views/performance/breadcrumb';
  23. import {getCurrentLandingDisplay, LandingDisplayField} from '../landing/utils';
  24. import Tab from './tabs';
  25. import TeamKeyTransactionButton from './teamKeyTransactionButton';
  26. import TransactionThresholdButton from './transactionThresholdButton';
  27. import {TransactionThresholdMetric} from './transactionThresholdModal';
  28. type Props = {
  29. currentTab: Tab;
  30. eventView: EventView;
  31. hasWebVitals: 'maybe' | 'yes' | 'no';
  32. location: Location;
  33. organization: Organization;
  34. projectId: string;
  35. projects: Project[];
  36. transactionName: string;
  37. metricsCardinality?: MetricsCardinalityContext;
  38. onChangeThreshold?: (threshold: number, metric: TransactionThresholdMetric) => void;
  39. };
  40. function TransactionHeader({
  41. eventView,
  42. organization,
  43. projects,
  44. projectId,
  45. metricsCardinality,
  46. location,
  47. transactionName,
  48. onChangeThreshold,
  49. currentTab,
  50. hasWebVitals,
  51. }: Props) {
  52. function handleCreateAlertSuccess() {
  53. trackAdvancedAnalyticsEvent('performance_views.summary.create_alert_clicked', {
  54. organization,
  55. });
  56. }
  57. const project = projects.find(p => p.id === projectId);
  58. const hasAnomalyDetection = organization.features?.includes(
  59. 'performance-anomaly-detection-ui'
  60. );
  61. const hasSessionReplay =
  62. organization.features.includes('session-replay-ui') && projectSupportsReplay(project);
  63. const hasProfiling =
  64. project &&
  65. organization.features.includes('profiling') &&
  66. isProfilingSupportedOrProjectHasProfiles(project);
  67. const getWebVitals = useCallback(
  68. (hasMeasurements: boolean) => {
  69. switch (hasWebVitals) {
  70. case 'maybe':
  71. // need to check if the web vitals tab should be shown
  72. // frontend projects should always show the web vitals tab
  73. if (
  74. getCurrentLandingDisplay(location, projects, eventView).field ===
  75. LandingDisplayField.FRONTEND_PAGELOAD
  76. ) {
  77. return true;
  78. }
  79. // if it is not a frontend project, then we check to see if there
  80. // are any web vitals associated with the transaction recently
  81. return hasMeasurements;
  82. case 'yes':
  83. // always show the web vitals tab
  84. return true;
  85. case 'no':
  86. default:
  87. // never show the web vitals tab
  88. return false;
  89. }
  90. },
  91. [hasWebVitals, location, projects, eventView]
  92. );
  93. const replaysCount = useReplaysCount({
  94. transactionNames: transactionName,
  95. organization,
  96. projectIds: project ? [Number(project.id)] : [],
  97. })[transactionName];
  98. return (
  99. <Layout.Header>
  100. <Layout.HeaderContent>
  101. <Breadcrumb
  102. organization={organization}
  103. location={location}
  104. transaction={{
  105. project: projectId,
  106. name: transactionName,
  107. }}
  108. tab={currentTab}
  109. />
  110. <Layout.Title>
  111. {project && (
  112. <IdBadge
  113. project={project}
  114. avatarSize={28}
  115. hideName
  116. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  117. />
  118. )}
  119. {transactionName}
  120. </Layout.Title>
  121. </Layout.HeaderContent>
  122. <Layout.HeaderActions>
  123. <ButtonBar gap={1}>
  124. <Feature organization={organization} features={['incidents']}>
  125. {({hasFeature}) =>
  126. hasFeature && !metricsCardinality?.isLoading ? (
  127. <CreateAlertFromViewButton
  128. size="sm"
  129. eventView={eventView}
  130. organization={organization}
  131. projects={projects}
  132. onClick={handleCreateAlertSuccess}
  133. referrer="performance"
  134. alertType="trans_duration"
  135. aria-label={t('Create Alert')}
  136. disableMetricDataset={
  137. metricsCardinality?.outcome?.forceTransactionsOnly
  138. }
  139. />
  140. ) : null
  141. }
  142. </Feature>
  143. <TeamKeyTransactionButton
  144. transactionName={transactionName}
  145. eventView={eventView}
  146. organization={organization}
  147. />
  148. <GuideAnchor target="project_transaction_threshold_override" position="bottom">
  149. <TransactionThresholdButton
  150. organization={organization}
  151. transactionName={transactionName}
  152. eventView={eventView}
  153. onChangeThreshold={onChangeThreshold}
  154. />
  155. </GuideAnchor>
  156. </ButtonBar>
  157. </Layout.HeaderActions>
  158. <HasMeasurementsQuery
  159. location={location}
  160. orgSlug={organization.slug}
  161. eventView={eventView}
  162. transaction={transactionName}
  163. type="web"
  164. >
  165. {({hasMeasurements}) => {
  166. const renderWebVitals = getWebVitals(!!hasMeasurements);
  167. return (
  168. <TabList
  169. hideBorder
  170. outerWrapStyles={{
  171. gridColumn: '1 / -1',
  172. }}
  173. >
  174. <Item key={Tab.TransactionSummary}>{t('Overview')}</Item>
  175. <Item key={Tab.Events}>{t('All Events')}</Item>
  176. <Item key={Tab.Tags}>{t('Tags')}</Item>
  177. <Item key={Tab.Spans}>{t('Spans')}</Item>
  178. <Item
  179. key={Tab.Anomalies}
  180. textValue={t('Anomalies')}
  181. hidden={!hasAnomalyDetection}
  182. >
  183. {t('Anomalies')}
  184. <FeatureBadge type="alpha" noTooltip />
  185. </Item>
  186. <Item
  187. key={Tab.WebVitals}
  188. textValue={t('Web Vitals')}
  189. hidden={!renderWebVitals}
  190. >
  191. {t('Web Vitals')}
  192. </Item>
  193. <Item key={Tab.Replays} textValue={t('Replays')} hidden={!hasSessionReplay}>
  194. {t('Replays')}
  195. <ReplayCountBadge count={replaysCount} />
  196. <ReplaysFeatureBadge noTooltip />
  197. </Item>
  198. <Item key={Tab.Profiling} textValue={t('Profiling')} hidden={!hasProfiling}>
  199. {t('Profiling')}
  200. <FeatureBadge type="beta" noTooltip />
  201. </Item>
  202. </TabList>
  203. );
  204. }}
  205. </HasMeasurementsQuery>
  206. </Layout.Header>
  207. );
  208. }
  209. export default TransactionHeader;