Skip to content

Commit 4fb28df

Browse files
authored
ref(metrics): Convert more shared code for metric issue details to hooks (#85385)
Requires #85377 Adds a util file to a new directory to pull out some shared logic for the correlated section and the metrics graph. Will also make it a bit easier to read through both files. For the new `useMetricAlertId` hook, it mimics a pattern for cron/uptime ([example](https://github.com/getsentry/sentry/blob/cdd59af574fedd32005429827aefc06cdceab811/static/app/views/issueDetails/streamline/issueCronCheckTimeline.tsx#L36)) where by we fetch any event in 90d to have the best shot of finding a `alert_rule_id` ctx key since they are identical across all events. That way, even if a user picks small date range, or bad query, we don't unload the graph when the event isn't found. All this refactoring is to make it more reviewable as I take a look at changing the graph design
1 parent 4779f43 commit 4fb28df

File tree

5 files changed

+98
-88
lines changed

5 files changed

+98
-88
lines changed

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

+1-3
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,13 @@ import {getReplayIdFromEvent} from 'sentry/utils/replays/getReplayIdFromEvent';
7171
import {useLocation} from 'sentry/utils/useLocation';
7272
import useOrganization from 'sentry/utils/useOrganization';
7373
import {useParams} from 'sentry/utils/useParams';
74+
import {MetricIssuesSection} from 'sentry/views/issueDetails/metricIssues/metricIssuesSection';
7475
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
7576
import {EventDetails} from 'sentry/views/issueDetails/streamline/eventDetails';
7677
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
7778
import {TraceDataSection} from 'sentry/views/issueDetails/traceDataSection';
7879
import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
7980

80-
import MetricIssuesSection from '../metricIssuesSection';
81-
8281
const LLMMonitoringSection = lazy(
8382
() => import('sentry/components/events/interfaces/llm-monitoring/llmMonitoringSection')
8483
);
@@ -230,7 +229,6 @@ export function EventDetailsContent({
230229
<MetricIssuesSection
231230
organization={organization}
232231
group={group}
233-
event={event}
234232
project={project}
235233
/>
236234
)}

static/app/views/issueDetails/streamline/metricIssueChart.tsx static/app/views/issueDetails/metricIssues/metricIssueChart.tsx

+9-45
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,36 @@
1-
import {Fragment, lazy, useMemo} from 'react';
1+
import {lazy} from 'react';
22
import {useTheme} from '@emotion/react';
33
import styled from '@emotion/styled';
4-
import moment from 'moment-timezone';
54

6-
import {DateTime} from 'sentry/components/dateTime';
75
import LazyLoad from 'sentry/components/lazyLoad';
8-
import {t} from 'sentry/locale';
96
import {space} from 'sentry/styles/space';
10-
import type {Event} from 'sentry/types/event';
117
import type {Group} from 'sentry/types/group';
128
import type {Project} from 'sentry/types/project';
139
import useApi from 'sentry/utils/useApi';
1410
import useOrganization from 'sentry/utils/useOrganization';
15-
import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
1611
import {
1712
getFilter,
1813
getPeriodInterval,
1914
} from 'sentry/views/alerts/rules/metric/details/utils';
20-
import {Dataset, TimePeriod} from 'sentry/views/alerts/rules/metric/types';
15+
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
2116
import {extractEventTypeFilterFromRule} from 'sentry/views/alerts/rules/metric/utils/getEventTypeFilter';
2217
import {isCrashFreeAlert} from 'sentry/views/alerts/rules/metric/utils/isCrashFreeAlert';
2318
import {useMetricRule} from 'sentry/views/alerts/rules/metric/utils/useMetricRule';
19+
import {
20+
useMetricIssueAlertId,
21+
useMetricTimePeriod,
22+
} from 'sentry/views/issueDetails/metricIssues/utils';
2423

2524
const MetricChart = lazy(
2625
() => import('sentry/views/alerts/rules/metric/details/metricChart')
2726
);
2827

29-
export function MetricIssueChart({
30-
event,
31-
group,
32-
project,
33-
}: {
34-
event: Event | undefined;
35-
group: Group;
36-
project: Project;
37-
}) {
28+
export function MetricIssuesChart({group, project}: {group: Group; project: Project}) {
3829
const theme = useTheme();
3930
const api = useApi();
4031
const organization = useOrganization();
4132

42-
const ruleId = event?.contexts?.metric_alert?.alert_rule_id;
43-
33+
const ruleId = useMetricIssueAlertId({groupId: group.id});
4434
const {data: rule} = useMetricRule(
4535
{
4636
orgSlug: organization.slug,
@@ -55,33 +45,7 @@ export function MetricIssueChart({
5545
enabled: !!ruleId,
5646
}
5747
);
58-
59-
const openPeriod = group.openPeriods?.[0];
60-
const timePeriod = useMemo((): TimePeriodType | null => {
61-
if (!openPeriod) {
62-
return null;
63-
}
64-
const start = openPeriod.start;
65-
let end = openPeriod.end;
66-
if (!end) {
67-
end = new Date().toISOString();
68-
}
69-
return {
70-
start,
71-
end,
72-
period: TimePeriod.SEVEN_DAYS,
73-
usingPeriod: false,
74-
label: t('Custom time'),
75-
display: (
76-
<Fragment>
77-
<DateTime date={moment.utc(start)} />
78-
{' — '}
79-
<DateTime date={moment.utc(end)} />
80-
</Fragment>
81-
),
82-
custom: true,
83-
};
84-
}, [openPeriod]);
48+
const timePeriod = useMetricTimePeriod({openPeriod: group.openPeriods?.[0]});
8549

8650
if (!rule || !timePeriod) {
8751
return null;

static/app/views/issueDetails/metricIssuesSection.tsx static/app/views/issueDetails/metricIssues/metricIssuesSection.tsx

+9-38
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,36 @@
1-
import {Fragment, useMemo} from 'react';
2-
import moment from 'moment-timezone';
3-
41
import {LinkButton} from 'sentry/components/button';
5-
import {DateTime} from 'sentry/components/dateTime';
62
import {t} from 'sentry/locale';
7-
import type {Event} from 'sentry/types/event';
83
import type {Group} from 'sentry/types/group';
94
import type {Organization} from 'sentry/types/organization';
105
import type {Project} from 'sentry/types/project';
116
import {useLocation} from 'sentry/utils/useLocation';
12-
import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
137
import RelatedIssues from 'sentry/views/alerts/rules/metric/details/relatedIssues';
148
import RelatedTransactions from 'sentry/views/alerts/rules/metric/details/relatedTransactions';
15-
import {Dataset, TimePeriod} from 'sentry/views/alerts/rules/metric/types';
9+
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
1610
import {extractEventTypeFilterFromRule} from 'sentry/views/alerts/rules/metric/utils/getEventTypeFilter';
1711
import {isCrashFreeAlert} from 'sentry/views/alerts/rules/metric/utils/isCrashFreeAlert';
1812
import {useMetricRule} from 'sentry/views/alerts/rules/metric/utils/useMetricRule';
13+
import {
14+
useMetricIssueAlertId,
15+
useMetricTimePeriod,
16+
} from 'sentry/views/issueDetails/metricIssues/utils';
1917
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
2018
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
2119

2220
interface MetricIssuesSectionProps {
23-
event: Event;
2421
group: Group;
2522
organization: Organization;
2623
project: Project;
2724
}
2825

29-
export default function MetricIssuesSection({
26+
export function MetricIssuesSection({
3027
organization,
31-
event,
3228
group,
3329
project,
3430
}: MetricIssuesSectionProps) {
3531
const location = useLocation();
36-
const ruleId = event.contexts?.metric_alert?.alert_rule_id;
32+
33+
const ruleId = useMetricIssueAlertId({groupId: group.id});
3734
const {data: rule} = useMetricRule(
3835
{
3936
orgSlug: organization.slug,
@@ -48,33 +45,7 @@ export default function MetricIssuesSection({
4845
enabled: !!ruleId,
4946
}
5047
);
51-
52-
const openPeriod = group.openPeriods?.[0];
53-
const timePeriod = useMemo((): TimePeriodType | null => {
54-
if (!openPeriod) {
55-
return null;
56-
}
57-
const start = openPeriod.start;
58-
let end = openPeriod.end;
59-
if (!end) {
60-
end = new Date().toISOString();
61-
}
62-
return {
63-
start,
64-
end,
65-
period: TimePeriod.SEVEN_DAYS,
66-
usingPeriod: false,
67-
label: t('Custom time'),
68-
display: (
69-
<Fragment>
70-
<DateTime date={moment.utc(start)} />
71-
{' — '}
72-
<DateTime date={moment.utc(end)} />
73-
</Fragment>
74-
),
75-
custom: true,
76-
};
77-
}, [openPeriod]);
48+
const timePeriod = useMetricTimePeriod({openPeriod: group.openPeriods?.[0]});
7849

7950
if (!rule || !timePeriod) {
8051
return null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {Fragment, useMemo} from 'react';
2+
import moment from 'moment-timezone';
3+
4+
import {DateTime} from 'sentry/components/dateTime';
5+
import {t} from 'sentry/locale';
6+
import type {Event} from 'sentry/types/event';
7+
import type {GroupOpenPeriod} from 'sentry/types/group';
8+
import {useApiQuery} from 'sentry/utils/queryClient';
9+
import useOrganization from 'sentry/utils/useOrganization';
10+
import {useUser} from 'sentry/utils/useUser';
11+
import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
12+
import {TimePeriod} from 'sentry/views/alerts/rules/metric/types';
13+
import {useIssueDetails} from 'sentry/views/issueDetails/streamline/context';
14+
import {getGroupEventQueryKey} from 'sentry/views/issueDetails/utils';
15+
16+
export function useMetricIssueAlertId({groupId}: {groupId: string}): string | undefined {
17+
/**
18+
* This should be removed once the metric alert rule ID is set on the issue.
19+
* This will fetch an event from the max range if the detector details
20+
* are not available (e.g. time range has changed and page refreshed)
21+
*/
22+
const user = useUser();
23+
const organization = useOrganization();
24+
const {detectorDetails} = useIssueDetails();
25+
const {detectorId, detectorType} = detectorDetails;
26+
27+
const hasMetricDetector = detectorId && detectorType === 'metric_alert';
28+
29+
const {data: event} = useApiQuery<Event>(
30+
getGroupEventQueryKey({
31+
orgSlug: organization.slug,
32+
groupId,
33+
eventId: user.options.defaultIssueEvent,
34+
environments: [],
35+
}),
36+
{
37+
staleTime: Infinity,
38+
enabled: !hasMetricDetector,
39+
retry: false,
40+
}
41+
);
42+
43+
// Fall back to the fetched event in case the provider doesn't have the detector details
44+
return hasMetricDetector ? detectorId : event?.contexts?.metric_alert?.alert_rule_id;
45+
}
46+
47+
export function useMetricTimePeriod({
48+
openPeriod,
49+
}: {
50+
openPeriod?: GroupOpenPeriod;
51+
}): TimePeriodType | null {
52+
return useMemo((): TimePeriodType | null => {
53+
if (!openPeriod) {
54+
return null;
55+
}
56+
const start = openPeriod.start;
57+
let end = openPeriod.end;
58+
if (!end) {
59+
end = new Date().toISOString();
60+
}
61+
return {
62+
start,
63+
end,
64+
period: TimePeriod.SEVEN_DAYS,
65+
usingPeriod: false,
66+
label: t('Custom time'),
67+
display: (
68+
<Fragment>
69+
<DateTime date={moment.utc(start)} />
70+
{' — '}
71+
<DateTime date={moment.utc(end)} />
72+
</Fragment>
73+
),
74+
custom: true,
75+
};
76+
}, [openPeriod]);
77+
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
1616
import {useLocation} from 'sentry/utils/useLocation';
1717
import {useNavigate} from 'sentry/utils/useNavigate';
1818
import useOrganization from 'sentry/utils/useOrganization';
19+
import {MetricIssuesChart} from 'sentry/views/issueDetails/metricIssues/metricIssueChart';
1920
import {useIssueDetails} from 'sentry/views/issueDetails/streamline/context';
2021
import {EventGraph} from 'sentry/views/issueDetails/streamline/eventGraph';
2122
import {
@@ -25,7 +26,6 @@ import {
2526
import {IssueCronCheckTimeline} from 'sentry/views/issueDetails/streamline/issueCronCheckTimeline';
2627
import IssueTagsPreview from 'sentry/views/issueDetails/streamline/issueTagsPreview';
2728
import {IssueUptimeCheckTimeline} from 'sentry/views/issueDetails/streamline/issueUptimeCheckTimeline';
28-
import {MetricIssueChart} from 'sentry/views/issueDetails/streamline/metricIssueChart';
2929
import {OccurrenceSummary} from 'sentry/views/issueDetails/streamline/occurrenceSummary';
3030
import {getDetectorDetails} from 'sentry/views/issueDetails/streamline/sidebar/detectorSection';
3131
import {ToggleSidebar} from 'sentry/views/issueDetails/streamline/sidebar/toggleSidebar';
@@ -121,7 +121,7 @@ export function EventDetailsHeader({group, event, project}: EventDetailsHeaderPr
121121
<EventGraph event={event} group={group} style={{flex: 1}} />
122122
)}
123123
{issueTypeConfig.header.graph.type === 'detector-history' && (
124-
<MetricIssueChart group={group} project={project} event={event} />
124+
<MetricIssuesChart group={group} project={project} />
125125
)}
126126
{issueTypeConfig.header.graph.type === 'uptime-checks' && (
127127
<IssueUptimeCheckTimeline group={group} />

0 commit comments

Comments
 (0)