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')}
{t('Web Vitals')}
{t('Replays')}
{t('Profiles')}
{t('Aggregate 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')}
{t('Web Vitals')}
{t('Replays')}
{t('Profiles')}
{t('Aggregate Spans')}
);
}}
);
}
const TransactionName = styled('div')`
${p => p.theme.overflowEllipsis}
`;
export default TransactionHeader;