Skip to content

Commit 3a410a5

Browse files
authored
feat(releases): Refactor to support deep-linked Issues Chart (#89711)
This PR creates a "chart widget" similar to what we did for Insights widgets (e.g. #89344). This is so that we can deep-link to the `EventGraph` using only `groupId` from URL parameters. This also makes some changes to `EventGraph` so that we can use a custom date range (/page filters). This is used when we click a release bubble, we want the chart in the drawer to refetch the same stats (but at a different interval) according to the bubble's time bucket. Part of #88560
1 parent 9acd5b8 commit 3a410a5

File tree

4 files changed

+105
-23
lines changed

4 files changed

+105
-23
lines changed

static/app/components/charts/chartWidgetLoader.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {useQuery} from '@tanstack/react-query';
44
import Placeholder from 'sentry/components/placeholder';
55
import {t} from 'sentry/locale';
66
import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
7+
import {EVENT_GRAPH_WIDGET_ID} from 'sentry/views/issueDetails/streamline/eventGraphWidget';
78

89
interface Props extends LoadableChartWidgetProps {
910
/**
@@ -26,7 +27,13 @@ interface Props extends LoadableChartWidgetProps {
2627
export function ChartWidgetLoader(props: Props) {
2728
const query = useQuery<{default: React.FC<LoadableChartWidgetProps>}>({
2829
queryKey: [`widget-${props.id}`],
29-
queryFn: () => import(`sentry/views/insights/common/components/widgets/${props.id}`),
30+
queryFn: () => {
31+
if (props.id === EVENT_GRAPH_WIDGET_ID) {
32+
return import('sentry/views/issueDetails/streamline/eventGraphWidget');
33+
}
34+
35+
return import(`sentry/views/insights/common/components/widgets/${props.id}`);
36+
},
3037
});
3138

3239
if (query.isPending) {

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

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type {ReactEchartsRef, SeriesDataUnit} from 'sentry/types/echarts';
2626
import type {Event} from 'sentry/types/event';
2727
import type {Group} from 'sentry/types/group';
2828
import type {EventsStats, MultiSeriesEventsStats} from 'sentry/types/organization';
29+
import type EventView from 'sentry/utils/discover/eventView';
2930
import {DiscoverDatasets} from 'sentry/utils/discover/types';
3031
import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
3132
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
@@ -48,7 +49,7 @@ import {Tab} from 'sentry/views/issueDetails/types';
4849
import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute';
4950
import {useReleaseBubbles} from 'sentry/views/releases/releaseBubbles/useReleaseBubbles';
5051

51-
const enum EventGraphSeries {
52+
enum EventGraphSeries {
5253
EVENT = 'event',
5354
USER = 'user',
5455
}
@@ -64,6 +65,7 @@ interface EventGraphProps {
6465
* chart).
6566
*/
6667
disableZoomNavigation?: boolean;
68+
eventView?: EventView;
6769
ref?: React.Ref<ReactEchartsRef>;
6870
/**
6971
* Configures showing releases on the chart as bubbles or lines. This is used
@@ -100,6 +102,7 @@ function createSeriesAndCount(stats: EventsStats) {
100102
export function EventGraph({
101103
group,
102104
event,
105+
eventView: eventViewProps,
103106
disableZoomNavigation = false,
104107
showReleasesAs,
105108
showSummary = true,
@@ -131,7 +134,8 @@ export function EventGraph({
131134
ref: chartContainerRef,
132135
onResize,
133136
});
134-
const eventView = useIssueDetailsEventView({group, isSmallContainer});
137+
const eventViewHook = useIssueDetailsEventView({group, isSmallContainer});
138+
const eventView = eventViewProps || eventViewHook;
135139

136140
const {
137141
data: groupStats = {},
@@ -447,18 +451,22 @@ export function EventGraph({
447451
if (isLoadingStats || isPendingUniqueUsersCount) {
448452
return (
449453
<GraphWrapper {...styleProps}>
450-
<SummaryContainer>
451-
<GraphButton
452-
isActive={visibleSeries === EventGraphSeries.EVENT}
453-
disabled
454-
label={t('Events')}
455-
/>
456-
<GraphButton
457-
isActive={visibleSeries === EventGraphSeries.USER}
458-
disabled
459-
label={t('Users')}
460-
/>
461-
</SummaryContainer>
454+
{showSummary ? (
455+
<SummaryContainer>
456+
<GraphButton
457+
isActive={visibleSeries === EventGraphSeries.EVENT}
458+
disabled
459+
label={t('Events')}
460+
/>
461+
<GraphButton
462+
isActive={visibleSeries === EventGraphSeries.USER}
463+
disabled
464+
label={t('Users')}
465+
/>
466+
</SummaryContainer>
467+
) : (
468+
<div />
469+
)}
462470
<LoadingChartContainer ref={chartContainerRef}>
463471
<Placeholder height="96px" testId="event-graph-loading" />
464472
</LoadingChartContainer>
@@ -561,7 +569,11 @@ function GraphButton({
561569
label,
562570
count,
563571
...props
564-
}: {isActive: boolean; label: string; count?: string} & Partial<ButtonProps>) {
572+
}: {
573+
isActive: boolean;
574+
label: string;
575+
count?: string;
576+
} & Partial<ButtonProps>) {
565577
return (
566578
<CalloutButton
567579
isActive={isActive}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import styled from '@emotion/styled';
2+
3+
import Placeholder from 'sentry/components/placeholder';
4+
import {t} from 'sentry/locale';
5+
import {useParams} from 'sentry/utils/useParams';
6+
import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
7+
import {EventGraph} from 'sentry/views/issueDetails/streamline/eventGraph';
8+
import {useIssueDetailsEventView} from 'sentry/views/issueDetails/streamline/hooks/useIssueDetailsDiscoverQuery';
9+
import {useGroup} from 'sentry/views/issueDetails/useGroup';
10+
11+
export default function EventGraphWidget({pageFilters}: LoadableChartWidgetProps) {
12+
const {groupId} = useParams();
13+
14+
const {
15+
data: groupData,
16+
isPending: loadingGroup,
17+
isError: isGroupError,
18+
} = useGroup({groupId: groupId!});
19+
20+
const eventView = useIssueDetailsEventView({
21+
group: groupData!,
22+
isSmallContainer: true,
23+
pageFilters,
24+
});
25+
26+
if (loadingGroup) {
27+
return (
28+
<Container>
29+
<Placeholder height="100%" />
30+
</Container>
31+
);
32+
}
33+
34+
if (isGroupError) {
35+
return (
36+
<Container>
37+
<Placeholder height="100%" error={t('Error loading chart')} />
38+
</Container>
39+
);
40+
}
41+
42+
return (
43+
<EventGraph
44+
event={undefined}
45+
eventView={eventView}
46+
group={groupData}
47+
showSummary={false}
48+
showReleasesAs="line"
49+
disableZoomNavigation
50+
/>
51+
);
52+
}
53+
54+
const Container = styled('div')`
55+
height: 100%;
56+
`;
57+
58+
export const EVENT_GRAPH_WIDGET_ID = 'event-graph-widget';

static/app/views/issueDetails/streamline/hooks/useIssueDetailsDiscoverQuery.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {getInterval} from 'sentry/components/charts/utils';
2+
import type {PageFilters} from 'sentry/types/core';
23
import type {Group} from 'sentry/types/group';
34
import type {NewQuery, SavedQuery} from 'sentry/types/organization';
45
import EventView from 'sentry/utils/discover/eventView';
@@ -16,11 +17,13 @@ import {useGroupDefaultStatsPeriod} from 'sentry/views/issueDetails/useGroupDefa
1617

1718
export function useIssueDetailsEventView({
1819
group,
20+
pageFilters,
1921
queryProps,
2022
isSmallContainer = false,
2123
}: {
2224
group: Group;
2325
isSmallContainer?: boolean;
26+
pageFilters?: PageFilters;
2427
queryProps?: Partial<SavedQuery>;
2528
}) {
2629
const searchQuery = useEventQuery({groupId: group.id});
@@ -29,13 +32,15 @@ export function useIssueDetailsEventView({
2932
const hasSetStatsPeriod =
3033
location.query.statsPeriod || location.query.start || location.query.end;
3134
const defaultStatsPeriod = useGroupDefaultStatsPeriod(group, group.project);
32-
const periodQuery = hasSetStatsPeriod
33-
? getPeriod({
34-
start: location.query.start as string,
35-
end: location.query.end as string,
36-
period: location.query.statsPeriod as string,
37-
})
38-
: defaultStatsPeriod;
35+
const periodQuery = pageFilters
36+
? getPeriod(pageFilters.datetime)
37+
: hasSetStatsPeriod
38+
? getPeriod({
39+
start: location.query.start as string,
40+
end: location.query.end as string,
41+
period: location.query.statsPeriod as string,
42+
})
43+
: defaultStatsPeriod;
3944

4045
const interval = getInterval(
4146
{

0 commit comments

Comments
 (0)