Skip to content

Commit 7aa0eb7

Browse files
authored
feat(issue-details): Allow filters for first/last/recommended (#82476)
This PR does a few things, but mostly addresses the inconsistencies with filtering in the streamlined UI. - Filters will now apply to the queries for First/Last/Recommended to ensure better results for the loaded event, matching the user expectations. These filters are env, date range, and queries. - The missing event state has been redesigned to be more helpful, letting users easily click to recommended/clear filters and adds a graphic. - The first/last/recommended buttons will now be accessible even when an event is not loaded **Specific event missing** <img width="1213" alt="image" src="https://github.com/user-attachments/assets/8ad3aedc-a159-4c51-b733-a693301b4779" /> **Query doesn't find anything** <img width="1182" alt="image" src="https://github.com/user-attachments/assets/9847c5c3-b6ef-4314-8c57-b032bc1da17e" /> **todo** - [x] Add or update tests
1 parent 30d47e5 commit 7aa0eb7

21 files changed

+431
-246
lines changed

static/app/views/issueDetails/groupDetails.tsx

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import useApi from 'sentry/utils/useApi';
4141
import {useDetailedProject} from 'sentry/utils/useDetailedProject';
4242
import {useLocation} from 'sentry/utils/useLocation';
4343
import {useMemoWithPrevious} from 'sentry/utils/useMemoWithPrevious';
44-
import {useNavigate} from 'sentry/utils/useNavigate';
4544
import useOrganization from 'sentry/utils/useOrganization';
4645
import {useParams} from 'sentry/utils/useParams';
4746
import useProjects from 'sentry/utils/useProjects';
@@ -223,8 +222,6 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
223222
const organization = useOrganization();
224223
const router = useRouter();
225224
const params = router.params;
226-
const navigate = useNavigate();
227-
const defaultIssueEvent = useDefaultIssueEvent();
228225

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

@@ -236,7 +233,6 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
236233
const {
237234
data: event,
238235
isPending: loadingEvent,
239-
isError: isEventError,
240236
refetch: refetchEvent,
241237
} = useGroupEvent({
242238
groupId,
@@ -252,27 +248,6 @@ function useFetchGroupDetails(): FetchGroupDetailsState {
252248
refetch: refetchGroupCall,
253249
} = useGroup({groupId});
254250

255-
useEffect(() => {
256-
const eventIdUrl = params.eventId ?? defaultIssueEvent;
257-
const isLatestOrRecommendedEvent =
258-
eventIdUrl === 'latest' || eventIdUrl === 'recommended';
259-
260-
if (isLatestOrRecommendedEvent && isEventError && router.location.query.query) {
261-
// If we get an error from the helpful event endpoint, it probably means
262-
// the query failed validation. We should remove the query to try again.
263-
navigate(
264-
{
265-
...router.location,
266-
query: {
267-
...router.location.query,
268-
query: undefined,
269-
},
270-
},
271-
{replace: true}
272-
);
273-
}
274-
}, [defaultIssueEvent, isEventError, navigate, router.location, params.eventId]);
275-
276251
/**
277252
* Allows the GroupEventHeader to display the previous event while the new event is loading.
278253
* This is not closer to the GroupEventHeader because it is unmounted

static/app/views/issueDetails/groupEventAttachments/groupEventAttachments.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function GroupEventAttachments({project, group}: GroupEventAttachmentsProps) {
3333
const location = useLocation();
3434
const organization = useOrganization();
3535
const hasStreamlinedUI = useHasStreamlinedUI();
36-
const eventQuery = useEventQuery({group});
36+
const eventQuery = useEventQuery({groupId: group.id});
3737
const eventView = useIssueDetailsEventView({group});
3838
const activeAttachmentsTab =
3939
(location.query.attachmentFilter as EventAttachmentFilter | undefined) ??

static/app/views/issueDetails/groupEventAttachments/useGroupEventAttachments.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export function useGroupEventAttachments({
106106
const hasStreamlinedUI = useHasStreamlinedUI();
107107
const location = useLocation();
108108
const organization = useOrganization();
109-
const eventQuery = useEventQuery({group});
109+
const eventQuery = useEventQuery({groupId: group.id});
110110
const eventView = useIssueDetailsEventView({group});
111111

112112
const fetchAllAvailable = hasStreamlinedUI ? options?.fetchAllAvailable : true;

static/app/views/issueDetails/groupEventDetails/groupEventDetails.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,11 @@ function GroupEventDetails() {
162162

163163
const renderContent = () => {
164164
if (isLoadingEvent) {
165-
if (hasStreamlinedUI) {
166-
return <GroupEventDetailsLoading />;
167-
}
168-
return <LoadingIndicator />;
165+
return hasStreamlinedUI ? <GroupEventDetailsLoading /> : <LoadingIndicator />;
169166
}
170167

171-
if (isEventError) {
168+
// The streamlined UI uses a different error interface
169+
if (isEventError && !hasStreamlinedUI) {
172170
return (
173171
<GroupEventDetailsLoadingError
174172
environments={environments}

static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function EventDetailsContent({
9090
group,
9191
event,
9292
project,
93-
}: Required<EventDetailsContentProps>) {
93+
}: Required<Pick<EventDetailsContentProps, 'group' | 'event' | 'project'>>) {
9494
const organization = useOrganization();
9595
const location = useLocation();
9696
const hasStreamlinedUI = useHasStreamlinedUI();
@@ -463,6 +463,10 @@ export default function GroupEventDetailsContent({
463463
}: EventDetailsContentProps) {
464464
const hasStreamlinedUI = useHasStreamlinedUI();
465465

466+
if (hasStreamlinedUI) {
467+
return <EventDetails event={event} group={group} project={project} />;
468+
}
469+
466470
if (!event) {
467471
return (
468472
<NotFoundMessage>
@@ -471,11 +475,7 @@ export default function GroupEventDetailsContent({
471475
);
472476
}
473477

474-
return hasStreamlinedUI ? (
475-
<EventDetails event={event} group={group} project={project} />
476-
) : (
477-
<EventDetailsContent group={group} event={event} project={project} />
478-
);
478+
return <EventDetailsContent group={group} event={event} project={project} />;
479479
}
480480

481481
/**

static/app/views/issueDetails/streamline/context.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export interface EventDetailsContextType extends EventDetailsState {
9292

9393
export const EventDetailsContext = createContext<EventDetailsContextType>({
9494
sectionData: {},
95+
navScrollMargin: 0,
96+
eventCount: 0,
9597
dispatch: () => {},
9698
});
9799

@@ -103,6 +105,7 @@ export interface EventDetailsState {
103105
sectionData: {
104106
[key in SectionKey]?: SectionConfig;
105107
};
108+
eventCount?: number;
106109
navScrollMargin?: number;
107110
}
108111

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

120-
export type EventDetailsActions = UpdateSectionAction | UpdateDetailsAction;
123+
type UpdateEventCountAction = {
124+
count: number;
125+
type: 'UPDATE_EVENT_COUNT';
126+
};
127+
128+
export type EventDetailsActions =
129+
| UpdateSectionAction
130+
| UpdateDetailsAction
131+
| UpdateEventCountAction;
121132

122133
function updateSection(
123134
state: EventDetailsState,
@@ -151,6 +162,8 @@ export function useEventDetailsReducer() {
151162
return updateSection(state, action.key, action.config ?? {});
152163
case 'UPDATE_DETAILS':
153164
return {...state, ...action.state};
165+
case 'UPDATE_EVENT_COUNT':
166+
return {...state, eventCount: action.count};
154167
default:
155168
return state;
156169
}

static/app/views/issueDetails/streamline/eventDetails.tsx

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,30 @@ import {
1313
EventDetailsContent,
1414
type EventDetailsContentProps,
1515
} from 'sentry/views/issueDetails/groupEventDetails/groupEventDetailsContent';
16-
import {
17-
EventDetailsContext,
18-
useEventDetails,
19-
useEventDetailsReducer,
20-
} from 'sentry/views/issueDetails/streamline/context';
16+
import {useEventDetails} from 'sentry/views/issueDetails/streamline/context';
17+
import {EventMissingBanner} from 'sentry/views/issueDetails/streamline/eventMissingBanner';
2118
import {EventTitle} from 'sentry/views/issueDetails/streamline/eventTitle';
2219

23-
export function EventDetails({
24-
group,
25-
event,
26-
project,
27-
}: Required<EventDetailsContentProps>) {
28-
const {eventDetails, dispatch} = useEventDetailsReducer();
20+
export function EventDetails({group, event, project}: EventDetailsContentProps) {
21+
if (!event) {
22+
return (
23+
<GroupContent role="main">
24+
<BannerPadding>
25+
<EventMissingBanner />
26+
</BannerPadding>
27+
</GroupContent>
28+
);
29+
}
2930

3031
return (
31-
<EventDetailsContext.Provider value={{...eventDetails, dispatch}}>
32-
<PageErrorBoundary mini message={t('There was an error loading the event content')}>
33-
<GroupContent role="main">
34-
<StickyEventNav event={event} group={group} />
35-
<ContentPadding>
36-
<EventDetailsContent group={group} event={event} project={project} />
37-
</ContentPadding>
38-
</GroupContent>
39-
</PageErrorBoundary>
40-
</EventDetailsContext.Provider>
32+
<PageErrorBoundary mini message={t('There was an error loading the event content')}>
33+
<GroupContent role="main">
34+
<StickyEventNav event={event} group={group} />
35+
<ContentPadding>
36+
<EventDetailsContent group={group} event={event} project={project} />
37+
</ContentPadding>
38+
</GroupContent>
39+
</PageErrorBoundary>
4140
);
4241
}
4342

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

99+
const BannerPadding = styled('div')`
100+
padding: 40px;
101+
`;
102+
100103
const PageErrorBoundary = styled(ErrorBoundary)`
101104
margin: 0;
102105
border: 1px solid ${p => p.theme.translucentBorder};

static/app/views/issueDetails/streamline/eventDetailsHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function EventDetailsHeader({
3636
const navigate = useNavigate();
3737
const location = useLocation();
3838
const environments = useEnvironmentsFromUrl();
39-
const searchQuery = useEventQuery({group});
39+
const searchQuery = useEventQuery({groupId: group.id});
4040
const {baseUrl} = useGroupDetailsRoute();
4141

4242
const issueTypeConfig = getConfigForIssueType(group, project);

static/app/views/issueDetails/streamline/eventGraph.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {type CSSProperties, useMemo, useState} from 'react';
1+
import {type CSSProperties, useEffect, useMemo, useState} from 'react';
22
import {useTheme} from '@emotion/react';
33
import styled from '@emotion/styled';
44
import Color from 'color';
@@ -26,6 +26,7 @@ import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
2626
import {useLocation} from 'sentry/utils/useLocation';
2727
import useOrganization from 'sentry/utils/useOrganization';
2828
import {getBucketSize} from 'sentry/views/dashboards/widgetCard/utils';
29+
import {useEventDetails} from 'sentry/views/issueDetails/streamline/context';
2930
import {useCurrentEventMarklineSeries} from 'sentry/views/issueDetails/streamline/hooks/useEventMarkLineSeries';
3031
import useFlagSeries from 'sentry/views/issueDetails/streamline/hooks/useFlagSeries';
3132
import {
@@ -74,6 +75,7 @@ export function EventGraph({group, event, ...styleProps}: EventGraphProps) {
7475
);
7576
const eventView = useIssueDetailsEventView({group});
7677
const config = getConfigForIssueType(group, group.project);
78+
const {dispatch} = useEventDetails();
7779

7880
const {
7981
data: groupStats = {},
@@ -136,6 +138,12 @@ export function EventGraph({group, event, ...styleProps}: EventGraphProps) {
136138
}
137139
return createSeriesAndCount(groupStats['count()']);
138140
}, [groupStats]);
141+
142+
// Ensure the dropdown can access the new filtered event count
143+
useEffect(() => {
144+
dispatch({type: 'UPDATE_EVENT_COUNT', count: eventCount});
145+
}, [eventCount, dispatch]);
146+
139147
const {series: unfilteredEventSeries} = useMemo(() => {
140148
if (!unfilteredGroupStats?.['count()']) {
141149
return {series: []};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {OrganizationFixture} from 'sentry-fixture/organization';
2+
import {RouterFixture} from 'sentry-fixture/routerFixture';
3+
4+
import {render, screen} from 'sentry-test/reactTestingLibrary';
5+
6+
import {EventMissingBanner} from 'sentry/views/issueDetails/streamline/eventMissingBanner';
7+
8+
describe('EventMissingBanner', () => {
9+
it('renders elements for known event IDs', () => {
10+
const organization = OrganizationFixture();
11+
const router = RouterFixture({params: {groupId: 'group-1', eventId: 'recommended'}});
12+
13+
render(<EventMissingBanner />, {organization, router});
14+
15+
// Header
16+
expect(screen.getByText(/We couldn't track down an event/)).toBeInTheDocument();
17+
// Body
18+
expect(screen.getByText(/here are some things to try/)).toBeInTheDocument();
19+
expect(screen.getByText(/Change up your filters./)).toBeInTheDocument();
20+
expect(screen.getByRole('link', {name: 'Clear event filters'})).toBeInTheDocument();
21+
// Image
22+
expect(screen.getByAltText('Compass illustration')).toBeInTheDocument();
23+
});
24+
25+
it('renders elements for specific event IDs', () => {
26+
const organization = OrganizationFixture();
27+
const router = RouterFixture({params: {groupId: 'group-1', eventId: 'abc123'}});
28+
29+
render(<EventMissingBanner />, {organization, router});
30+
31+
// Header
32+
expect(screen.getByText(/We couldn't track down that event/)).toBeInTheDocument();
33+
expect(screen.getByText(/(abc123)/)).toBeInTheDocument();
34+
// Body
35+
expect(screen.getByText(/here are some things to try/)).toBeInTheDocument();
36+
expect(screen.getByText(/Double check the event ID./)).toBeInTheDocument();
37+
expect(
38+
screen.getByRole('link', {name: 'View recommended event'})
39+
).toBeInTheDocument();
40+
// Image
41+
expect(screen.getByAltText('Compass illustration')).toBeInTheDocument();
42+
});
43+
});

0 commit comments

Comments
 (0)