import {Fragment} from 'react'; import styled from '@emotion/styled'; import {LinkButton} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import Count from 'sentry/components/count'; import DropdownButton from 'sentry/components/dropdownButton'; import {DropdownMenu} from 'sentry/components/dropdownMenu'; import {IconTelescope} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import {trackAnalytics} from 'sentry/utils/analytics'; import {SavedQueryDatasets} from 'sentry/utils/discover/types'; import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig'; import parseLinkHeader from 'sentry/utils/parseLinkHeader'; import {keepPreviousData} from 'sentry/utils/queryClient'; import useReplayCountForIssues from 'sentry/utils/replayCount/useReplayCountForIssues'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {hasDatasetSelector} from 'sentry/views/dashboards/utils'; import {useGroupEventAttachments} from 'sentry/views/issueDetails/groupEventAttachments/useGroupEventAttachments'; import {useIssueDetails} from 'sentry/views/issueDetails/streamline/context'; import {useIssueDetailsEventView} from 'sentry/views/issueDetails/streamline/hooks/useIssueDetailsDiscoverQuery'; import {IssueDetailsEventNavigation} from 'sentry/views/issueDetails/streamline/issueDetailsEventNavigation'; import {Tab, TabPaths} from 'sentry/views/issueDetails/types'; import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute'; interface IssueEventNavigationProps { event: Event | undefined; group: Group; } export function IssueEventNavigation({event, group}: IssueEventNavigationProps) { const organization = useOrganization(); const {baseUrl, currentTab} = useGroupDetailsRoute(); const location = useLocation(); const eventView = useIssueDetailsEventView({group}); const {eventCount} = useIssueDetails(); const issueTypeConfig = getConfigForIssueType(group, group.project); const hideDropdownButton = !issueTypeConfig.attachments.enabled && !issueTypeConfig.userFeedback.enabled && !issueTypeConfig.replays.enabled; const discoverUrl = eventView.getResultsViewUrlTarget( organization.slug, false, hasDatasetSelector(organization) ? SavedQueryDatasets.ERRORS : undefined ); const {getReplayCountForIssue} = useReplayCountForIssues({ statsPeriod: '90d', }); const replaysCount = getReplayCountForIssue(group.id, group.issueCategory) ?? 0; const attachments = useGroupEventAttachments({ group, activeAttachmentsTab: 'all', options: {placeholderData: keepPreviousData}, }); const attachmentPagination = parseLinkHeader( attachments.getResponseHeader?.('Link') ?? null ); // Since we reuse whatever page the user was on, we can look at pagination to determine if there are more attachments const hasManyAttachments = attachmentPagination.next?.results || attachmentPagination.previous?.results; const TabName: Partial> = { [Tab.DETAILS]: issueTypeConfig.customCopy.eventUnits, [Tab.EVENTS]: issueTypeConfig.customCopy.eventUnits, [Tab.REPLAYS]: t('Replays'), [Tab.ATTACHMENTS]: t('Attachments'), [Tab.USER_FEEDBACK]: t('Feedback'), }; const allEventsPath = `${baseUrl}${TabPaths[issueTypeConfig.allEventsPath]}`; return ( { trackAnalytics('issue_details.issue_content_selected', { organization, // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message content: TabName[key], }); }} items={[ { key: Tab.DETAILS, label: ( {TabName[Tab.DETAILS]} ), textValue: TabName[Tab.DETAILS], to: { ...location, pathname: `${baseUrl}${TabPaths[Tab.DETAILS]}`, }, }, { key: Tab.REPLAYS, label: ( {TabName[Tab.REPLAYS]}{' '} {replaysCount > 50 ? ( 50+ ) : ( )} ), textValue: TabName[Tab.REPLAYS], to: { ...location, pathname: `${baseUrl}${TabPaths[Tab.REPLAYS]}`, }, hidden: !issueTypeConfig.replays.enabled, }, { key: Tab.ATTACHMENTS, label: ( {TabName[Tab.ATTACHMENTS]} {hasManyAttachments ? '50+' : attachments.attachments.length} ), textValue: TabName[Tab.ATTACHMENTS], to: { ...location, pathname: `${baseUrl}${TabPaths[Tab.ATTACHMENTS]}`, }, hidden: !issueTypeConfig.attachments.enabled, }, { key: Tab.USER_FEEDBACK, label: ( {TabName[Tab.USER_FEEDBACK]} ), textValue: TabName[Tab.USER_FEEDBACK], to: { ...location, pathname: `${baseUrl}${TabPaths[Tab.USER_FEEDBACK]}`, }, hidden: !issueTypeConfig.userFeedback.enabled, }, ]} offset={[-2, 1]} trigger={(triggerProps, isOpen) => hideDropdownButton ? ( {TabName[currentTab] ?? TabName[Tab.DETAILS]} ) : ( {TabName[currentTab] ?? TabName[Tab.DETAILS]} ) } /> {t('in this issue')} {currentTab === Tab.DETAILS && ( {t('All %s', issueTypeConfig.customCopy.eventUnits)} )} {currentTab === issueTypeConfig.allEventsPath && ( {currentTab === Tab.EVENTS && ( } analyticsEventKey="issue_details.discover_clicked" analyticsEventName="Issue Details: Discover Clicked" > {t('Discover')} )} {t('Close')} )} ); } const LargeDropdownButtonWrapper = styled('div')` display: flex; align-items: center; gap: ${space(0.25)}; `; const NavigationDropdownButton = styled(DropdownButton)` font-size: ${p => p.theme.fontSizeLarge}; font-weight: ${p => p.theme.fontWeightBold}; padding-right: ${space(0.5)}; `; const NavigationLabel = styled('div')` font-size: ${p => p.theme.fontSizeLarge}; font-weight: ${p => p.theme.fontWeightBold}; padding-right: ${space(0.25)}; padding-left: ${space(1.5)}; `; const LargeInThisIssueText = styled('div')` font-size: ${p => p.theme.fontSizeLarge}; font-weight: ${p => p.theme.fontWeightBold}; color: ${p => p.theme.subText}; `; const EventNavigationWrapper = styled('div')` flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; font-size: ${p => p.theme.fontSizeSmall}; @media (min-width: ${p => p.theme.breakpoints.xsmall}) { flex-direction: row; align-items: center; } `; const NavigationWrapper = styled('div')` display: flex; gap: ${space(0.25)}; justify-content: space-between; @media (min-width: ${p => p.theme.breakpoints.xsmall}) { gap: ${space(0.5)}; } `; const DropdownCountWrapper = styled('div')<{isCurrentTab: boolean}>` display: flex; align-items: center; justify-content: space-between; gap: ${space(3)}; font-variant-numeric: tabular-nums; font-weight: ${p => p.isCurrentTab ? p.theme.fontWeightBold : p.theme.fontWeightNormal}; `; const ItemCount = styled(Count)` color: ${p => p.theme.subText}; `; const CustomItemCount = styled('div')` color: ${p => p.theme.subText}; `;