123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- import {Fragment, useCallback, useEffect, useMemo, useState} from 'react';
- import {css, useTheme} from '@emotion/react';
- import styled from '@emotion/styled';
- import {LinkButton} from 'sentry/components/button';
- import {TabList, Tabs} from 'sentry/components/tabs';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconChevron} 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 normalizeUrl from 'sentry/utils/url/normalizeUrl';
- import {useLocation} from 'sentry/utils/useLocation';
- import useMedia from 'sentry/utils/useMedia';
- import useOrganization from 'sentry/utils/useOrganization';
- import {useParams} from 'sentry/utils/useParams';
- import {useGroupEvent} from 'sentry/views/issueDetails/useGroupEvent';
- import {useDefaultIssueEvent} from 'sentry/views/issueDetails/utils';
- const enum EventNavOptions {
- RECOMMENDED = 'recommended',
- LATEST = 'latest',
- OLDEST = 'oldest',
- CUSTOM = 'custom',
- }
- const EventNavOrder = [
- EventNavOptions.OLDEST,
- EventNavOptions.LATEST,
- EventNavOptions.RECOMMENDED,
- EventNavOptions.CUSTOM,
- ];
- interface IssueDetailsEventNavigationProps {
- event: Event | undefined;
- group: Group;
- }
- export function IssueDetailsEventNavigation({
- event,
- group,
- }: IssueDetailsEventNavigationProps) {
- const organization = useOrganization();
- const location = useLocation();
- const params = useParams<{eventId?: string}>();
- const theme = useTheme();
- const defaultIssueEvent = useDefaultIssueEvent();
- const isSmallScreen = useMedia(`(max-width: ${theme.breakpoints.small})`);
- const [shouldPreload, setShouldPreload] = useState({next: false, previous: false});
- // Reset shouldPreload when the groupId changes
- useEffect(() => {
- setShouldPreload({next: false, previous: false});
- }, [group.id]);
- // Prefetch next
- useGroupEvent({
- groupId: group.id,
- eventId: event?.nextEventID ?? undefined,
- options: {enabled: shouldPreload.next},
- });
- // Prefetch previous
- useGroupEvent({
- groupId: group.id,
- eventId: event?.previousEventID ?? undefined,
- options: {enabled: shouldPreload.previous},
- });
- const handleHoverPagination = useCallback(
- (direction: 'next' | 'previous', isEnabled: boolean) => () => {
- if (isEnabled) {
- setShouldPreload(prev => ({...prev, [direction]: true}));
- }
- },
- []
- );
- const selectedOption = useMemo(() => {
- switch (params.eventId) {
- case EventNavOptions.RECOMMENDED:
- case EventNavOptions.LATEST:
- case EventNavOptions.OLDEST:
- return params.eventId;
- case undefined:
- return defaultIssueEvent;
- default:
- return EventNavOptions.CUSTOM;
- }
- }, [params.eventId, defaultIssueEvent]);
- const EventNavLabels = {
- [EventNavOptions.RECOMMENDED]: isSmallScreen ? t('Rec.') : t('Recommended'),
- [EventNavOptions.OLDEST]: t('First'),
- [EventNavOptions.LATEST]: t('Last'),
- };
- const EventNavTooltips = {
- [EventNavOptions.RECOMMENDED]: t('Recommended event matching filters'),
- [EventNavOptions.OLDEST]: t('First event matching filters'),
- [EventNavOptions.LATEST]: t('Last event matching filters'),
- };
- const onTabChange = (tabKey: typeof selectedOption) => {
- trackAnalytics('issue_details.event_navigation_selected', {
- organization,
- content: EventNavLabels[tabKey as keyof typeof EventNavLabels],
- });
- };
- const baseEventsPath = `/organizations/${organization.slug}/issues/${group.id}/events/`;
- const grayText = css`
- color: ${theme.subText};
- font-weight: ${theme.fontWeightNormal};
- `;
- return (
- <Fragment>
- <Navigation>
- <Tooltip title={t('Previous Event')} skipWrapper>
- <LinkButton
- aria-label={t('Previous Event')}
- borderless
- size="xs"
- icon={<IconChevron direction="left" />}
- disabled={!defined(event?.previousEventID)}
- analyticsEventKey="issue_details.previous_event_clicked"
- analyticsEventName="Issue Details: Previous Event Clicked"
- to={{
- pathname: `${baseEventsPath}${event?.previousEventID}/`,
- query: {...location.query, referrer: 'previous-event'},
- }}
- css={grayText}
- onMouseEnter={handleHoverPagination(
- 'previous',
- defined(event?.previousEventID)
- )}
- onClick={() => {
- // Assume they will continue to paginate
- setShouldPreload({next: true, previous: true});
- }}
- />
- </Tooltip>
- <Tooltip title={t('Next Event')} skipWrapper>
- <LinkButton
- aria-label={t('Next Event')}
- borderless
- size="xs"
- icon={<IconChevron direction="right" />}
- disabled={!defined(event?.nextEventID)}
- analyticsEventKey="issue_details.next_event_clicked"
- analyticsEventName="Issue Details: Next Event Clicked"
- to={{
- pathname: `${baseEventsPath}${event?.nextEventID}/`,
- query: {...location.query, referrer: 'next-event'},
- }}
- css={grayText}
- onMouseEnter={handleHoverPagination('next', defined(event?.nextEventID))}
- onClick={() => {
- // Assume they will continue to paginate
- setShouldPreload({next: true, previous: true});
- }}
- />
- </Tooltip>
- </Navigation>
- <Tabs value={selectedOption} disableOverflow onChange={onTabChange}>
- <TabList hideBorder variant="floating">
- {EventNavOrder.map(label => {
- const eventPath =
- label === selectedOption
- ? undefined
- : {
- pathname: normalizeUrl(baseEventsPath + label + '/'),
- query: {...location.query, referrer: `${label}-event`},
- };
- return (
- <TabList.Item
- to={eventPath}
- key={label}
- hidden={label === EventNavOptions.CUSTOM}
- textValue={EventNavLabels[label as keyof typeof EventNavLabels]}
- >
- <Tooltip
- title={EventNavTooltips[label as keyof typeof EventNavTooltips]}
- skipWrapper
- >
- {EventNavLabels[label as keyof typeof EventNavLabels]}
- </Tooltip>
- </TabList.Item>
- );
- })}
- </TabList>
- </Tabs>
- </Fragment>
- );
- }
- const Navigation = styled('div')`
- display: flex;
- padding-right: ${space(0.25)};
- border-right: 1px solid ${p => p.theme.gray100};
- `;
|