header.tsx 6.4 KB

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