import {Fragment, useCallback} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; import Feature from 'sentry/components/acl/feature'; import GuideAnchor from 'sentry/components/assistant/guideAnchor'; import ButtonBar from 'sentry/components/buttonBar'; import {CreateAlertFromViewButton} from 'sentry/components/createAlertButton'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; import IdBadge from 'sentry/components/idBadge'; import * as Layout from 'sentry/components/layouts/thirds'; import ReplayCountBadge from 'sentry/components/replays/replayCountBadge'; import {TabList} from 'sentry/components/tabs'; import {Tooltip} from 'sentry/components/tooltip'; import {t} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; import type EventView from 'sentry/utils/discover/eventView'; import type {MetricsCardinalityContext} from 'sentry/utils/performance/contexts/metricsCardinality'; import HasMeasurementsQuery from 'sentry/utils/performance/vitals/hasMeasurementsQuery'; import {isProfilingSupportedOrProjectHasProfiles} from 'sentry/utils/profiling/platforms'; import useReplayCountForTransactions from 'sentry/utils/replayCount/useReplayCountForTransactions'; import projectSupportsReplay from 'sentry/utils/replays/projectSupportsReplay'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import {useNavigate} from 'sentry/utils/useNavigate'; import {AiHeader} from 'sentry/views/insights/pages/ai/aiPageHeader'; import {AI_LANDING_SUB_PATH} from 'sentry/views/insights/pages/ai/settings'; import {BackendHeader} from 'sentry/views/insights/pages/backend/backendPageHeader'; import {BACKEND_LANDING_SUB_PATH} from 'sentry/views/insights/pages/backend/settings'; import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader'; import {FRONTEND_LANDING_SUB_PATH} from 'sentry/views/insights/pages/frontend/settings'; import {MobileHeader} from 'sentry/views/insights/pages/mobile/mobilePageHeader'; import {MOBILE_LANDING_SUB_PATH} from 'sentry/views/insights/pages/mobile/settings'; import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import Breadcrumb, {getTabCrumbs} from 'sentry/views/performance/breadcrumb'; import {aggregateWaterfallRouteWithQuery} from 'sentry/views/performance/transactionSummary/aggregateSpanWaterfall/utils'; import {TAB_ANALYTICS} from 'sentry/views/performance/transactionSummary/pageLayout'; import {eventsRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionEvents/utils'; import {profilesRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionProfiles/utils'; import {replaysRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionReplays/utils'; import {spansRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionSpans/utils'; import {tagsRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionTags/utils'; import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils'; import {getSelectedProjectPlatforms} from 'sentry/views/performance/utils'; import {getCurrentLandingDisplay, LandingDisplayField} from '../landing/utils'; import Tab from './tabs'; import TeamKeyTransactionButton from './teamKeyTransactionButton'; import TransactionThresholdButton from './transactionThresholdButton'; import type {TransactionThresholdMetric} from './transactionThresholdModal'; export type Props = { currentTab: Tab; eventView: EventView; hasWebVitals: 'maybe' | 'yes' | 'no'; location: Location; organization: Organization; projectId: string; projects: Project[]; transactionName: string; metricsCardinality?: MetricsCardinalityContext; onChangeThreshold?: (threshold: number, metric: TransactionThresholdMetric) => void; }; function TransactionHeader({ eventView, organization, projects, projectId, metricsCardinality, location, transactionName, onChangeThreshold, currentTab, hasWebVitals, }: Props) { const {isInDomainView, view} = useDomainViewFilters(); const navigate = useNavigate(); const getNewRoute = useCallback( (newTab: Tab) => { if (!transactionName) { return {}; } const routeQuery = { orgSlug: organization.slug, transaction: transactionName, projectID: projectId, query: location.query, view, }; switch (newTab) { case Tab.TAGS: return tagsRouteWithQuery(routeQuery); case Tab.EVENTS: return eventsRouteWithQuery(routeQuery); case Tab.SPANS: return spansRouteWithQuery(routeQuery); case Tab.REPLAYS: return replaysRouteWithQuery(routeQuery); case Tab.PROFILING: { return profilesRouteWithQuery(routeQuery); } case Tab.AGGREGATE_WATERFALL: return aggregateWaterfallRouteWithQuery(routeQuery); case Tab.TRANSACTION_SUMMARY: default: return transactionSummaryRouteWithQuery(routeQuery); } }, [location.query, organization.slug, projectId, transactionName, view] ); const onTabChange = useCallback( (newTab: string) => { // Prevent infinite rerenders if (newTab === currentTab) { return; } const analyticsKey = TAB_ANALYTICS[newTab]; if (analyticsKey) { trackAnalytics(analyticsKey, { organization, project_platforms: getSelectedProjectPlatforms(location, projects), }); } navigate(normalizeUrl(getNewRoute(newTab as Tab))); }, [getNewRoute, organization, location, projects, currentTab, navigate] ); function handleCreateAlertSuccess() { trackAnalytics('performance_views.summary.create_alert_clicked', { organization, }); } const project = projects.find(p => p.id === projectId); const hasSessionReplay = organization.features.includes('session-replay') && project && projectSupportsReplay(project); const hasProfiling = project && organization.features.includes('profiling') && isProfilingSupportedOrProjectHasProfiles(project); const hasAggregateWaterfall = organization.features.includes( 'insights-initial-modules' ); const getWebVitals = useCallback( (hasMeasurements: boolean) => { switch (hasWebVitals) { case 'maybe': // need to check if the web vitals tab should be shown // frontend projects should always show the web vitals tab if ( getCurrentLandingDisplay(location, projects, eventView).field === LandingDisplayField.FRONTEND_OTHER ) { return true; } // if it is not a frontend project, then we check to see if there // are any web vitals associated with the transaction recently return hasMeasurements; case 'yes': // always show the web vitals tab return true; case 'no': default: // never show the web vitals tab return false; } }, [hasWebVitals, location, projects, eventView] ); const {getReplayCountForTransaction} = useReplayCountForTransactions({ statsPeriod: '90d', }); const replaysCount = getReplayCountForTransaction(transactionName); const tabList = ( {({hasMeasurements}) => { const renderWebVitals = getWebVitals(!!hasMeasurements); return ( {t('Overview')} {t('Sampled Events')} {t('Tags')} {t('Spans')} ); }} ); if (isInDomainView) { const headerProps = { headerTitle: ( {project && ( )} {transactionName} ), hideDefaultTabs: true, tabs: { onTabChange, tabList, value: currentTab, }, breadcrumbs: getTabCrumbs({ location, organization, tab: currentTab, transaction: { name: transactionName, project: projectId, }, view, }), headerActions: ( {({hasFeature}) => hasFeature && !metricsCardinality?.isLoading ? ( ) : null } ), }; if (view === FRONTEND_LANDING_SUB_PATH) { return ; } if (view === BACKEND_LANDING_SUB_PATH) { return ; } if (view === AI_LANDING_SUB_PATH) { return ; } if (view === MOBILE_LANDING_SUB_PATH) { return ; } } return ( {project && ( )} {transactionName} {({hasFeature}) => hasFeature && !metricsCardinality?.isLoading ? ( ) : null } {({hasMeasurements}) => { const renderWebVitals = getWebVitals(!!hasMeasurements); return ( {t('Overview')} {t('Sampled Events')} {t('Tags')} {t('Spans')} ); }} ); } const TransactionName = styled('div')` ${p => p.theme.overflowEllipsis} `; export default TransactionHeader;