import styled from '@emotion/styled'; import omit from 'lodash/omit'; import {Button, LinkButton} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import {Chevron} from 'sentry/components/chevron'; import {DropdownMenu} from 'sentry/components/dropdownMenu'; import {TabList, Tabs} from 'sentry/components/tabs'; import TimeSince from 'sentry/components/timeSince'; import {IconChevron, IconCopy} 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 {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import { getAnalyticsDataForEvent, getAnalyticsDataForGroup, getShortEventId, } from 'sentry/utils/events'; import {getReplayIdFromEvent} from 'sentry/utils/replays/getReplayIdFromEvent'; import useCopyToClipboard from 'sentry/utils/useCopyToClipboard'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; type EventNavigationProps = { event: Event; group: Group; }; type SectionDefinition = { condition: (event: Event) => boolean; label: string; section: string; }; enum EventNavOptions { RECOMMENDED = 'recommended', LATEST = 'latest', OLDEST = 'oldest', } const EventNavLabels = { [EventNavOptions.RECOMMENDED]: t('Recommended Event'), [EventNavOptions.OLDEST]: t('First Event'), [EventNavOptions.LATEST]: t('Last Event'), }; const eventDataSections: SectionDefinition[] = [ {section: 'event-highlights', label: t('Event Highlights'), condition: () => true}, { section: 'stacktrace', label: t('Stack Trace'), condition: (event: Event) => event.entries.some(entry => entry.type === 'stacktrace'), }, { section: 'exception', label: t('Exception'), condition: (event: Event) => event.entries.some(entry => entry.type === 'exception'), }, { section: 'breadcrumbs', label: t('Breadcrumbs'), condition: (event: Event) => event.entries.some(entry => entry.type === 'breadcrumbs'), }, {section: 'tags', label: t('Tags'), condition: (event: Event) => event.tags.length > 0}, {section: 'context', label: t('Context'), condition: (event: Event) => !!event.context}, { section: 'user-feedback', label: t('User Feedback'), condition: (event: Event) => !!event.userReport, }, { section: 'replay', label: t('Replay'), condition: (event: Event) => !!getReplayIdFromEvent(event), }, ]; export default function EventNavigation({event, group}: EventNavigationProps) { const location = useLocation(); const organization = useOrganization(); const hasPreviousEvent = defined(event.previousEventID); const hasNextEvent = defined(event.nextEventID); const baseEventsPath = `/organizations/${organization.slug}/issues/${group.id}/events/`; const jumpToSections = eventDataSections.filter(eventSection => eventSection.condition(event) ); const downloadJson = () => { const host = organization.links.regionUrl; const jsonUrl = `${host}/api/0/projects/${organization.slug}/${group.project.slug}/events/${event.id}/json/`; window.open(jsonUrl); trackAnalytics('issue_details.event_json_clicked', { organization, group_id: parseInt(`${event.groupID}`, 10), }); }; const {onClick: copyLink} = useCopyToClipboard({ successMessage: t('Event URL copied to clipboard'), text: window.location.origin + normalizeUrl(`${baseEventsPath}${event.id}/`), onCopy: () => trackAnalytics('issue_details.copy_event_link_clicked', { organization, ...getAnalyticsDataForGroup(group), ...getAnalyticsDataForEvent(event), }), }); const {onClick: copyEventId} = useCopyToClipboard({ successMessage: t('Event ID copied to clipboard'), text: event.id, }); return (