import {Component, Fragment} from 'react'; // eslint-disable-next-line no-restricted-imports import {withRouter, WithRouterProps} from 'react-router'; import styled from '@emotion/styled'; import omit from 'lodash/omit'; import {fetchOrgMembers} from 'sentry/actionCreators/members'; import {Client} from 'sentry/api'; import Feature from 'sentry/components/acl/feature'; import AssigneeSelector from 'sentry/components/assigneeSelector'; import GuideAnchor from 'sentry/components/assistant/guideAnchor'; 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 EventAnnotation from 'sentry/components/events/eventAnnotation'; import EventMessage from 'sentry/components/events/eventMessage'; import InboxReason from 'sentry/components/group/inboxBadges/inboxReason'; import UnhandledInboxTag from 'sentry/components/group/inboxBadges/unhandledTag'; import ProjectBadge from 'sentry/components/idBadge/projectBadge'; import * as Layout from 'sentry/components/layouts/thirds'; import Link from 'sentry/components/links/link'; import ListLink from 'sentry/components/links/listLink'; import NavTabs from 'sentry/components/navTabs'; import ReplaysFeatureBadge from 'sentry/components/replays/replaysFeatureBadge'; import SeenByList from 'sentry/components/seenByList'; import ShortId from 'sentry/components/shortId'; import Tooltip from 'sentry/components/tooltip'; import {IconChat} from 'sentry/icons'; import {t} from 'sentry/locale'; import space from 'sentry/styles/space'; import {Group, Organization, Project} from 'sentry/types'; import {Event} from 'sentry/types/event'; import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent'; import {getUtcDateString} from 'sentry/utils/dates'; import {getMessage} from 'sentry/utils/events'; import withApi from 'sentry/utils/withApi'; import withOrganization from 'sentry/utils/withOrganization'; import GroupActions from './actions'; import {Tab} from './types'; import {TagAndMessageWrapper} from './unhandledTag'; import {ReprocessingStatus} from './utils'; type Props = WithRouterProps & { api: Client; baseUrl: string; currentTab: string; group: Group; groupReprocessingStatus: ReprocessingStatus; organization: Organization; project: Project; replaysCount: number | null; event?: Event; }; type MemberList = NonNullable< React.ComponentProps['memberList'] >; type State = { memberList?: MemberList; }; class GroupHeader extends Component { state: State = {}; componentDidMount() { const {group, api, organization} = this.props; const {project} = group; fetchOrgMembers(api, organization.slug, [project.id]).then(memberList => { const users = memberList.map(member => member.user); this.setState({memberList: users}); }); } trackAssign: React.ComponentProps['onAssign'] = () => { const {group, project, organization, location} = this.props; const {alert_date, alert_rule_id, alert_type} = location.query; trackAdvancedAnalyticsEvent('issue_details.action_clicked', { organization, project_id: parseInt(project.id, 10), group_id: parseInt(group.id, 10), action_type: 'assign', // Alert properties track if the user came from email/slack alerts alert_date: typeof alert_date === 'string' ? getUtcDateString(Number(alert_date)) : undefined, alert_rule_id: typeof alert_rule_id === 'string' ? alert_rule_id : undefined, alert_type: typeof alert_type === 'string' ? alert_type : undefined, }); }; getDisabledTabs() { const {organization} = this.props; const hasReprocessingV2Feature = organization.features.includes('reprocessing-v2'); if (!hasReprocessingV2Feature) { return []; } const {groupReprocessingStatus} = this.props; if (groupReprocessingStatus === ReprocessingStatus.REPROCESSING) { return [ Tab.ACTIVITY, Tab.USER_FEEDBACK, Tab.ATTACHMENTS, Tab.EVENTS, Tab.MERGED, Tab.GROUPING, Tab.SIMILAR_ISSUES, Tab.TAGS, ]; } if (groupReprocessingStatus === ReprocessingStatus.REPROCESSED_AND_HASNT_EVENT) { return [ Tab.DETAILS, Tab.ATTACHMENTS, Tab.EVENTS, Tab.MERGED, Tab.GROUPING, Tab.SIMILAR_ISSUES, Tab.TAGS, Tab.USER_FEEDBACK, ]; } return []; } render() { const { project, group, currentTab, baseUrl, event, organization, location, replaysCount, } = this.props; const projectFeatures = new Set(project ? project.features : []); const organizationFeatures = new Set(organization ? organization.features : []); const userCount = group.userCount; const hasGroupingTreeUI = organizationFeatures.has('grouping-tree-ui'); const hasSimilarView = projectFeatures.has('similarity-view'); const hasEventAttachments = organizationFeatures.has('event-attachments'); let className = 'group-detail'; if (group.hasSeen) { className += ' hasSeen'; } if (group.status === 'resolved') { className += ' isResolved'; } const {memberList} = this.state; const message = getMessage(group); const searchTermWithoutQuery = omit(location.query, 'query'); const eventRouteToObject = { pathname: `${baseUrl}events/`, query: searchTermWithoutQuery, }; const disabledTabs = this.getDisabledTabs(); const disableActions = !!disabledTabs.length; const shortIdBreadCrumb = group.shortId && ( ); return (

{group.inbox && ( )}
{group.level && } {group.isUnhandled && } {group.logger && ( {group.logger} )} } />
{t('Events')}
{disableActions ? ( ) : ( )}
{t('Users')}
{userCount !== 0 ? ( disableActions ? ( ) : ( ) ) : ( 0 )}
{t('Assignee')}
currentTab === Tab.DETAILS} disabled={disabledTabs.includes(Tab.DETAILS)} > {t('Details')} currentTab === Tab.ACTIVITY} disabled={disabledTabs.includes(Tab.ACTIVITY)} > {t('Activity')} {group.numComments} currentTab === Tab.USER_FEEDBACK} disabled={disabledTabs.includes(Tab.USER_FEEDBACK)} > {t('User Feedback')} {hasEventAttachments && ( currentTab === Tab.ATTACHMENTS} disabled={disabledTabs.includes(Tab.ATTACHMENTS)} > {t('Attachments')} )} currentTab === Tab.TAGS} disabled={disabledTabs.includes(Tab.TAGS)} > {t('Tags')} currentTab === Tab.EVENTS} disabled={disabledTabs.includes(Tab.EVENTS)} > {t('Events')} currentTab === Tab.MERGED} disabled={disabledTabs.includes(Tab.MERGED)} > {t('Merged Issues')} {hasGroupingTreeUI && ( currentTab === Tab.GROUPING} disabled={disabledTabs.includes(Tab.GROUPING)} > {t('Grouping')} )} {hasSimilarView && ( currentTab === Tab.SIMILAR_ISSUES} disabled={disabledTabs.includes(Tab.SIMILAR_ISSUES)} > {t('Similar Issues')} )} currentTab === Tab.REPLAYS} > {t('Replays')}
); } } export default withApi(withRouter(withOrganization(GroupHeader))); const TitleWrapper = styled('div')` display: flex; line-height: 24px; `; const StyledBreadcrumbs = styled(Breadcrumbs)` margin-bottom: ${space(2)}; `; const IssueBreadcrumbWrapper = styled('div')` display: flex; align-items: center; `; const StyledTooltip = styled(Tooltip)` display: flex; `; const StyledShortId = styled(ShortId)` font-family: ${p => p.theme.text.family}; font-size: ${p => p.theme.fontSizeMedium}; `; const StatsWrapper = styled('div')` display: grid; justify-content: flex-end; grid-template-columns: repeat(3, min-content); gap: ${space(3)}; margin-right: 15px; `; const InboxReasonWrapper = styled('div')` margin-left: ${space(1)}; `; const StyledTagAndMessageWrapper = styled(TagAndMessageWrapper)` display: grid; grid-auto-flow: column; gap: ${space(1)}; justify-content: flex-start; line-height: 1.2; @media (max-width: ${p => p.theme.breakpoints.small}) { margin-bottom: ${space(2)}; } `; const StyledListLink = styled(ListLink)` svg { margin-left: ${space(0.5)}; margin-bottom: ${space(0.25)}; vertical-align: middle; } `; const StyledProjectBadge = styled(ProjectBadge)` flex-shrink: 0; `; const BreadcrumbProjectBadge = styled(StyledProjectBadge)` margin-right: ${space(0.75)}; `; const EventAnnotationWithSpace = styled(EventAnnotation)` margin-left: ${space(1)}; `;