Skip to content

Commit 660ada4

Browse files
snigdhasscttcpergetsantry[bot]
authored andcommitted
feat(metric-issues): Add "correlated issues" section (#83353)
<img width="956" alt="image" src="https://github.com/user-attachments/assets/8e06af3b-0499-4a6d-9c41-612c9856c80f" /> --------- Co-authored-by: Scott Cooper <[email protected]> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
1 parent ace9180 commit 660ada4

File tree

5 files changed

+162
-7
lines changed

5 files changed

+162
-7
lines changed

Diff for: static/app/utils/issueTypeConfig/metricIssueConfig.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const metricIssueConfig: IssueCategoryConfigMapping = {
2828
userFeedback: {enabled: false},
2929
usesIssuePlatform: true,
3030
stats: {enabled: false},
31+
tags: {enabled: false},
3132
tagsTab: {enabled: false},
3233
issueSummary: {enabled: false},
3334
},

Diff for: static/app/views/alerts/rules/metric/details/relatedIssues.tsx

+17-7
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,17 @@ interface Props {
2929
rule: MetricRule;
3030
timePeriod: TimePeriodType;
3131
query?: string;
32+
skipHeader?: boolean;
3233
}
3334

34-
function RelatedIssues({rule, organization, projects, query, timePeriod}: Props) {
35+
function RelatedIssues({
36+
rule,
37+
organization,
38+
projects,
39+
query,
40+
timePeriod,
41+
skipHeader,
42+
}: Props) {
3543
const router = useRouter();
3644

3745
// Add environment to the query parameters to be picked up by GlobalSelectionLink
@@ -96,12 +104,14 @@ function RelatedIssues({rule, organization, projects, query, timePeriod}: Props)
96104

97105
return (
98106
<Fragment>
99-
<ControlsWrapper>
100-
<StyledSectionHeading>{t('Related Issues')}</StyledSectionHeading>
101-
<LinkButton data-test-id="issues-open" size="xs" to={issueSearch}>
102-
{t('Open in Issues')}
103-
</LinkButton>
104-
</ControlsWrapper>
107+
{!skipHeader && (
108+
<ControlsWrapper>
109+
<StyledSectionHeading>{t('Related Issues')}</StyledSectionHeading>
110+
<LinkButton data-test-id="issues-open" size="xs" to={issueSearch}>
111+
{t('Open in Issues')}
112+
</LinkButton>
113+
</ControlsWrapper>
114+
)}
105115

106116
<TableWrapper>
107117
<GroupList

Diff for: static/app/views/issueDetails/correlatedIssues.tsx

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {Fragment, useMemo} from 'react';
2+
import moment from 'moment-timezone';
3+
4+
import {LinkButton} from 'sentry/components/button';
5+
import {DateTime} from 'sentry/components/dateTime';
6+
import {t} from 'sentry/locale';
7+
import type {Event} from 'sentry/types/event';
8+
import type {Group} from 'sentry/types/group';
9+
import type {Organization} from 'sentry/types/organization';
10+
import type {Project} from 'sentry/types/project';
11+
import {useApiQuery} from 'sentry/utils/queryClient';
12+
import type {TimePeriodType} from 'sentry/views/alerts/rules/metric/details/constants';
13+
import RelatedIssues from 'sentry/views/alerts/rules/metric/details/relatedIssues';
14+
import {
15+
Dataset,
16+
type MetricRule,
17+
TimePeriod,
18+
} from 'sentry/views/alerts/rules/metric/types';
19+
import {extractEventTypeFilterFromRule} from 'sentry/views/alerts/rules/metric/utils/getEventTypeFilter';
20+
import {isCrashFreeAlert} from 'sentry/views/alerts/rules/metric/utils/isCrashFreeAlert';
21+
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
22+
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
23+
24+
interface CorrelatedIssuesProps {
25+
event: Event;
26+
group: Group;
27+
organization: Organization;
28+
project: Project;
29+
}
30+
31+
export default function CorrelatedIssues({
32+
organization,
33+
event,
34+
group,
35+
project,
36+
}: CorrelatedIssuesProps) {
37+
const ruleId = event.contexts?.metric_alert?.alert_rule_id;
38+
const {data: rule} = useApiQuery<MetricRule>(
39+
[
40+
`/organizations/${organization.slug}/alert-rules/${ruleId}/`,
41+
{
42+
query: {
43+
expand: 'latestIncident',
44+
},
45+
},
46+
],
47+
{
48+
staleTime: Infinity,
49+
retry: false,
50+
}
51+
);
52+
53+
const openPeriod = group.openPeriods?.[0];
54+
const timePeriod = useMemo((): TimePeriodType | null => {
55+
if (!openPeriod) {
56+
return null;
57+
}
58+
const start = openPeriod.start;
59+
let end = openPeriod.end;
60+
if (!end) {
61+
end = new Date().toISOString();
62+
}
63+
return {
64+
start,
65+
end,
66+
period: TimePeriod.SEVEN_DAYS,
67+
usingPeriod: false,
68+
label: t('Custom time'),
69+
display: (
70+
<Fragment>
71+
<DateTime date={moment.utc(start)} />
72+
{' — '}
73+
<DateTime date={moment.utc(end)} />
74+
</Fragment>
75+
),
76+
custom: true,
77+
};
78+
}, [openPeriod]);
79+
80+
if (!rule || !timePeriod) {
81+
return null;
82+
}
83+
84+
const {dataset, query} = rule;
85+
86+
if (![Dataset.METRICS, Dataset.SESSIONS, Dataset.ERRORS].includes(dataset)) {
87+
return null;
88+
}
89+
90+
const queryParams = {
91+
start: timePeriod.start,
92+
end: timePeriod.end,
93+
groupStatsPeriod: 'auto',
94+
...(rule.environment ? {environment: rule.environment} : {}),
95+
sort: rule.aggregate === 'count_unique(user)' ? 'user' : 'freq',
96+
query,
97+
project: [project.id],
98+
};
99+
const issueSearch = {
100+
pathname: `/organizations/${organization.slug}/issues/`,
101+
query: queryParams,
102+
};
103+
104+
const actions = (
105+
<LinkButton data-test-id="issues-open" size="xs" to={issueSearch}>
106+
{t('Open in Issues')}
107+
</LinkButton>
108+
);
109+
110+
return (
111+
<InterimSection
112+
title={t('Correlated Issues')}
113+
type={SectionKey.CORRELATED_ISSUES}
114+
help={t('A list of issues that are correlated with this event')}
115+
actions={actions}
116+
>
117+
<RelatedIssues
118+
organization={organization}
119+
rule={rule}
120+
projects={[project]}
121+
timePeriod={timePeriod}
122+
query={
123+
dataset === Dataset.ERRORS
124+
? // Not using (query) AND (event.type:x) because issues doesn't support it yet
125+
`${extractEventTypeFilterFromRule(rule)} ${query}`.trim()
126+
: isCrashFreeAlert(dataset)
127+
? `${query} error.unhandled:true`.trim()
128+
: undefined
129+
}
130+
skipHeader
131+
/>
132+
</InterimSection>
133+
);
134+
}

Diff for: static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery
7070
import {getReplayIdFromEvent} from 'sentry/utils/replays/getReplayIdFromEvent';
7171
import {useLocation} from 'sentry/utils/useLocation';
7272
import useOrganization from 'sentry/utils/useOrganization';
73+
import CorrelatedIssues from 'sentry/views/issueDetails/correlatedIssues';
7374
import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
7475
import {EventDetails} from 'sentry/views/issueDetails/streamline/eventDetails';
7576
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
@@ -221,6 +222,14 @@ export function EventDetailsContent({
221222
project={project}
222223
/>
223224
)}
225+
{event.contexts?.metric_alert?.alert_rule_id && (
226+
<CorrelatedIssues
227+
organization={organization}
228+
group={group}
229+
event={event}
230+
project={project}
231+
/>
232+
)}
224233

225234
<EventEvidence event={event} group={group} project={project} />
226235
{defined(eventEntries[EntryType.MESSAGE]) && (

Diff for: static/app/views/issueDetails/streamline/context.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const enum SectionKey {
1919
UPTIME = 'uptime', // Only Uptime issues
2020
DOWNTIME = 'downtime',
2121
CRON_TIMELINE = 'cron-timeline', // Only Cron issues
22+
CORRELATED_ISSUES = 'correlated-issues', // Only Metric issues
2223

2324
HIGHLIGHTS = 'highlights',
2425
RESOURCES = 'resources', // Position controlled by flag

0 commit comments

Comments
 (0)