import {useMemo} from 'react'; import styled from '@emotion/styled'; import type {LocationDescriptor} from 'history'; import omit from 'lodash/omit'; import Badge from 'sentry/components/badge'; import {Breadcrumbs} from 'sentry/components/breadcrumbs'; import Count from 'sentry/components/count'; import EventOrGroupTitle from 'sentry/components/eventOrGroupTitle'; import ErrorLevel from 'sentry/components/events/errorLevel'; import EventMessage from 'sentry/components/events/eventMessage'; import {GroupStatusBadge} from 'sentry/components/group/inboxBadges/statusBadge'; import UnhandledInboxTag from 'sentry/components/group/inboxBadges/unhandledTag'; import * as Layout from 'sentry/components/layouts/thirds'; import Link from 'sentry/components/links/link'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import ReplayCountBadge from 'sentry/components/replays/replayCountBadge'; import {TabList} from 'sentry/components/tabs'; import {IconChat} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Event, Group, Organization, Project} from 'sentry/types'; import {IssueCategory} from 'sentry/types'; import {getMessage} from 'sentry/utils/events'; import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig'; import useReplayCountForIssues from 'sentry/utils/replayCount/useReplayCountForIssues'; import {projectCanLinkToReplay} from 'sentry/utils/replays/projectSupportsReplay'; import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import GroupActions from './actions'; import {ShortIdBreadrcumb} from './shortIdBreadcrumb'; import {Tab} from './types'; import {TagAndMessageWrapper} from './unhandledTag'; import {ReprocessingStatus} from './utils'; type Props = { baseUrl: string; group: Group; groupReprocessingStatus: ReprocessingStatus; organization: Organization; project: Project; event?: Event; }; interface GroupHeaderTabsProps extends Pick { disabledTabs: Tab[]; eventRoute: LocationDescriptor; } function GroupHeaderTabs({ baseUrl, disabledTabs, eventRoute, group, project, }: GroupHeaderTabsProps) { const organization = useOrganization(); const {getReplayCountForIssue} = useReplayCountForIssues(); const replaysCount = getReplayCountForIssue(group.id); const projectFeatures = new Set(project ? project.features : []); const organizationFeatures = new Set(organization ? organization.features : []); const hasSimilarView = projectFeatures.has('similarity-view'); const hasEventAttachments = organizationFeatures.has('event-attachments'); const hasReplaySupport = organizationFeatures.has('session-replay') && projectCanLinkToReplay(project); const issueTypeConfig = getConfigForIssueType(group, project); useRouteAnalyticsParams({ group_has_replay: (replaysCount ?? 0) > 0, }); return ( {t('Details')} {t('Activity')} {group.numComments} ); } function GroupHeader({ baseUrl, group, groupReprocessingStatus, organization, event, project, }: Props) { const location = useLocation(); const disabledTabs = useMemo(() => { const hasReprocessingV2Feature = organization.features.includes('reprocessing-v2'); if (!hasReprocessingV2Feature) { return []; } if (groupReprocessingStatus === ReprocessingStatus.REPROCESSING) { return [ Tab.ACTIVITY, Tab.USER_FEEDBACK, Tab.ATTACHMENTS, Tab.EVENTS, Tab.MERGED, Tab.SIMILAR_ISSUES, Tab.TAGS, ]; } if (groupReprocessingStatus === ReprocessingStatus.REPROCESSED_AND_HASNT_EVENT) { return [ Tab.DETAILS, Tab.ATTACHMENTS, Tab.EVENTS, Tab.MERGED, Tab.SIMILAR_ISSUES, Tab.TAGS, Tab.USER_FEEDBACK, ]; } return []; }, [organization, groupReprocessingStatus]); const eventRoute = useMemo(() => { const searchTermWithoutQuery = omit(location.query, 'query'); return { pathname: `${baseUrl}events/`, query: searchTermWithoutQuery, }; }, [location, baseUrl]); const {userCount} = group; let className = 'group-detail'; if (group.hasSeen) { className += ' hasSeen'; } if (group.status === 'resolved') { className += ' isResolved'; } const message = getMessage(group); const disableActions = !!disabledTabs.length; const shortIdBreadcrumb = ( ); const issueTypeConfig = getConfigForIssueType(group, project); return (

{group.level && } {group.isUnhandled && }
{issueTypeConfig.stats.enabled && (
{t('Events')}
{t('Users')}
{userCount !== 0 ? ( ) : ( 0 )}
)}
{/* Environment picker for mobile */}
); } export default GroupHeader; const BreadcrumbActionWrapper = styled('div')` display: flex; flex-direction: row; justify-content: space-between; gap: ${space(1)}; align-items: center; `; const HeaderRow = styled('div')` display: flex; gap: ${space(2)}; justify-content: space-between; margin-top: ${space(2)}; @media (max-width: ${p => p.theme.breakpoints.small}) { flex-direction: column; } `; const TitleWrapper = styled('div')` @media (min-width: ${p => p.theme.breakpoints.small}) { max-width: 65%; } `; const TitleHeading = styled('div')` display: flex; line-height: 2; gap: ${space(1)}; `; const StyledEventOrGroupTitle = styled(EventOrGroupTitle)` font-size: inherit; `; const StatsWrapper = styled('div')` display: grid; grid-template-columns: repeat(2, min-content); gap: calc(${space(3)} + ${space(3)}); @media (min-width: ${p => p.theme.breakpoints.small}) { justify-content: flex-end; } `; const StyledTagAndMessageWrapper = styled(TagAndMessageWrapper)` display: flex; gap: ${space(1)}; justify-content: flex-start; line-height: 1.2; `; const IconBadge = styled(Badge)` display: flex; align-items: center; gap: ${space(0.5)}; `; const StyledTabList = styled(TabList)` margin-top: ${space(2)}; `;