Skip to content

feat(issue-details): Allow filters for first/last/recommended #82476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 2, 2025
25 changes: 0 additions & 25 deletions static/app/views/issueDetails/groupDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import useApi from 'sentry/utils/useApi';
import {useDetailedProject} from 'sentry/utils/useDetailedProject';
import {useLocation} from 'sentry/utils/useLocation';
import {useMemoWithPrevious} from 'sentry/utils/useMemoWithPrevious';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {useParams} from 'sentry/utils/useParams';
import useProjects from 'sentry/utils/useProjects';
Expand Down Expand Up @@ -223,8 +222,6 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
const organization = useOrganization();
const router = useRouter();
const params = router.params;
const navigate = useNavigate();
const defaultIssueEvent = useDefaultIssueEvent();

const [allProjectChanged, setAllProjectChanged] = useState<boolean>(false);

Expand All @@ -236,7 +233,6 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
const {
data: event,
isPending: loadingEvent,
isError: isEventError,
refetch: refetchEvent,
} = useGroupEvent({
groupId,
Expand All @@ -252,27 +248,6 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
refetch: refetchGroupCall,
} = useGroup({groupId});

useEffect(() => {
const eventIdUrl = params.eventId ?? defaultIssueEvent;
const isLatestOrRecommendedEvent =
eventIdUrl === 'latest' || eventIdUrl === 'recommended';

if (isLatestOrRecommendedEvent && isEventError && router.location.query.query) {
// If we get an error from the helpful event endpoint, it probably means
// the query failed validation. We should remove the query to try again.
navigate(
{
...router.location,
query: {
...router.location.query,
query: undefined,
},
},
{replace: true}
);
}
}, [defaultIssueEvent, isEventError, navigate, router.location, params.eventId]);

/**
* Allows the GroupEventHeader to display the previous event while the new event is loading.
* This is not closer to the GroupEventHeader because it is unmounted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function GroupEventAttachments({project, group}: GroupEventAttachmentsProps) {
const location = useLocation();
const organization = useOrganization();
const hasStreamlinedUI = useHasStreamlinedUI();
const eventQuery = useEventQuery({group});
const eventQuery = useEventQuery({groupId: group.id});
const eventView = useIssueDetailsEventView({group});
const activeAttachmentsTab =
(location.query.attachmentFilter as EventAttachmentFilter | undefined) ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export function useGroupEventAttachments({
const hasStreamlinedUI = useHasStreamlinedUI();
const location = useLocation();
const organization = useOrganization();
const eventQuery = useEventQuery({group});
const eventQuery = useEventQuery({groupId: group.id});
const eventView = useIssueDetailsEventView({group});

const fetchAllAvailable = hasStreamlinedUI ? options?.fetchAllAvailable : true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,11 @@ function GroupEventDetails() {

const renderContent = () => {
if (isLoadingEvent) {
if (hasStreamlinedUI) {
return <GroupEventDetailsLoading />;
}
return <LoadingIndicator />;
return hasStreamlinedUI ? <GroupEventDetailsLoading /> : <LoadingIndicator />;
}

if (isEventError) {
// The streamlined UI uses a different error interface
if (isEventError && !hasStreamlinedUI) {
return (
<GroupEventDetailsLoadingError
environments={environments}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function EventDetailsContent({
group,
event,
project,
}: Required<EventDetailsContentProps>) {
}: Required<Pick<EventDetailsContentProps, 'group' | 'event' | 'project'>>) {
const organization = useOrganization();
const location = useLocation();
const hasStreamlinedUI = useHasStreamlinedUI();
Expand Down Expand Up @@ -463,6 +463,10 @@ export default function GroupEventDetailsContent({
}: EventDetailsContentProps) {
const hasStreamlinedUI = useHasStreamlinedUI();

if (hasStreamlinedUI) {
return <EventDetails event={event} group={group} project={project} />;
}

if (!event) {
return (
<NotFoundMessage>
Expand All @@ -471,11 +475,7 @@ export default function GroupEventDetailsContent({
);
}

return hasStreamlinedUI ? (
<EventDetails event={event} group={group} project={project} />
) : (
<EventDetailsContent group={group} event={event} project={project} />
);
return <EventDetailsContent group={group} event={event} project={project} />;
}

/**
Expand Down
15 changes: 14 additions & 1 deletion static/app/views/issueDetails/streamline/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export interface EventDetailsContextType extends EventDetailsState {

export const EventDetailsContext = createContext<EventDetailsContextType>({
sectionData: {},
navScrollMargin: 0,
eventCount: 0,
dispatch: () => {},
});

Expand All @@ -103,6 +105,7 @@ export interface EventDetailsState {
sectionData: {
[key in SectionKey]?: SectionConfig;
};
eventCount?: number;
navScrollMargin?: number;
}

Expand All @@ -117,7 +120,15 @@ type UpdateDetailsAction = {
state?: Omit<EventDetailsState, 'sectionData'>;
};

export type EventDetailsActions = UpdateSectionAction | UpdateDetailsAction;
type UpdateEventCountAction = {
count: number;
type: 'UPDATE_EVENT_COUNT';
};

export type EventDetailsActions =
| UpdateSectionAction
| UpdateDetailsAction
| UpdateEventCountAction;

function updateSection(
state: EventDetailsState,
Expand Down Expand Up @@ -151,6 +162,8 @@ export function useEventDetailsReducer() {
return updateSection(state, action.key, action.config ?? {});
case 'UPDATE_DETAILS':
return {...state, ...action.state};
case 'UPDATE_EVENT_COUNT':
return {...state, eventCount: action.count};
default:
return state;
}
Expand Down
45 changes: 24 additions & 21 deletions static/app/views/issueDetails/streamline/eventDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,30 @@ import {
EventDetailsContent,
type EventDetailsContentProps,
} from 'sentry/views/issueDetails/groupEventDetails/groupEventDetailsContent';
import {
EventDetailsContext,
useEventDetails,
useEventDetailsReducer,
} from 'sentry/views/issueDetails/streamline/context';
import {useEventDetails} from 'sentry/views/issueDetails/streamline/context';
import {EventMissingBanner} from 'sentry/views/issueDetails/streamline/eventMissingBanner';
import {EventTitle} from 'sentry/views/issueDetails/streamline/eventTitle';

export function EventDetails({
group,
event,
project,
}: Required<EventDetailsContentProps>) {
const {eventDetails, dispatch} = useEventDetailsReducer();
export function EventDetails({group, event, project}: EventDetailsContentProps) {
if (!event) {
return (
<GroupContent role="main">
<BannerPadding>
<EventMissingBanner />
</BannerPadding>
</GroupContent>
);
}

return (
<EventDetailsContext.Provider value={{...eventDetails, dispatch}}>
<PageErrorBoundary mini message={t('There was an error loading the event content')}>
<GroupContent role="main">
<StickyEventNav event={event} group={group} />
<ContentPadding>
<EventDetailsContent group={group} event={event} project={project} />
</ContentPadding>
</GroupContent>
</PageErrorBoundary>
</EventDetailsContext.Provider>
<PageErrorBoundary mini message={t('There was an error loading the event content')}>
<GroupContent role="main">
<StickyEventNav event={event} group={group} />
<ContentPadding>
<EventDetailsContent group={group} event={event} project={project} />
</ContentPadding>
</GroupContent>
</PageErrorBoundary>
);
}

Expand Down Expand Up @@ -97,6 +96,10 @@ const ContentPadding = styled('div')`
padding: ${space(1)} ${space(1.5)};
`;

const BannerPadding = styled('div')`
padding: 40px;
`;

const PageErrorBoundary = styled(ErrorBoundary)`
margin: 0;
border: 1px solid ${p => p.theme.translucentBorder};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function EventDetailsHeader({
const navigate = useNavigate();
const location = useLocation();
const environments = useEnvironmentsFromUrl();
const searchQuery = useEventQuery({group});
const searchQuery = useEventQuery({groupId: group.id});
const {baseUrl} = useGroupDetailsRoute();

const issueTypeConfig = getConfigForIssueType(group, project);
Expand Down
10 changes: 9 additions & 1 deletion static/app/views/issueDetails/streamline/eventGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {type CSSProperties, useMemo, useState} from 'react';
import {type CSSProperties, useEffect, useMemo, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import Color from 'color';
Expand Down Expand Up @@ -26,6 +26,7 @@ import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {getBucketSize} from 'sentry/views/dashboards/widgetCard/utils';
import {useEventDetails} from 'sentry/views/issueDetails/streamline/context';
import {useCurrentEventMarklineSeries} from 'sentry/views/issueDetails/streamline/hooks/useEventMarkLineSeries';
import useFlagSeries from 'sentry/views/issueDetails/streamline/hooks/useFlagSeries';
import {
Expand Down Expand Up @@ -74,6 +75,7 @@ export function EventGraph({group, event, ...styleProps}: EventGraphProps) {
);
const eventView = useIssueDetailsEventView({group});
const config = getConfigForIssueType(group, group.project);
const {dispatch} = useEventDetails();

const {
data: groupStats = {},
Expand Down Expand Up @@ -136,6 +138,12 @@ export function EventGraph({group, event, ...styleProps}: EventGraphProps) {
}
return createSeriesAndCount(groupStats['count()']);
}, [groupStats]);

// Ensure the dropdown can access the new filtered event count
useEffect(() => {
dispatch({type: 'UPDATE_EVENT_COUNT', count: eventCount});
}, [eventCount, dispatch]);

const {series: unfilteredEventSeries} = useMemo(() => {
if (!unfilteredGroupStats?.['count()']) {
return {series: []};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {OrganizationFixture} from 'sentry-fixture/organization';
import {RouterFixture} from 'sentry-fixture/routerFixture';

import {render, screen} from 'sentry-test/reactTestingLibrary';

import {EventMissingBanner} from 'sentry/views/issueDetails/streamline/eventMissingBanner';

describe('EventMissingBanner', () => {
it('renders elements for known event IDs', () => {
const organization = OrganizationFixture();
const router = RouterFixture({params: {groupId: 'group-1', eventId: 'recommended'}});

render(<EventMissingBanner />, {organization, router});

// Header
expect(screen.getByText(/We couldn't track down an event/)).toBeInTheDocument();
// Body
expect(screen.getByText(/here are some things to try/)).toBeInTheDocument();
expect(screen.getByText(/Change up your filters./)).toBeInTheDocument();
expect(screen.getByRole('link', {name: 'Clear event filters'})).toBeInTheDocument();
// Image
expect(screen.getByAltText('Compass illustration')).toBeInTheDocument();
});

it('renders elements for specific event IDs', () => {
const organization = OrganizationFixture();
const router = RouterFixture({params: {groupId: 'group-1', eventId: 'abc123'}});

render(<EventMissingBanner />, {organization, router});

// Header
expect(screen.getByText(/We couldn't track down that event/)).toBeInTheDocument();
expect(screen.getByText(/(abc123)/)).toBeInTheDocument();
// Body
expect(screen.getByText(/here are some things to try/)).toBeInTheDocument();
expect(screen.getByText(/Double check the event ID./)).toBeInTheDocument();
expect(
screen.getByRole('link', {name: 'View recommended event'})
).toBeInTheDocument();
// Image
expect(screen.getByAltText('Compass illustration')).toBeInTheDocument();
});
});
Loading
Loading