Skip to content

Commit 55ccd5f

Browse files
authored
feat(issue-details): Show supergroup in issue details sidebar (#112543)
Adds a compact supergroup card to the bottom of the issue details sidebar when the current issue belongs to a supergroup. Uses the existing `useSuperGroups` hook to fetch data for the single group ID and renders the error type, title, code area, and issue count. Clicking the card opens the existing supergroup detail drawer. Everything is behind the `top-issues-ui` feature flag - no request is made and nothing renders without it. <img width="369" height="258" alt="image" src="https://github.com/user-attachments/assets/972f6084-4faf-494e-acbd-0377b9e055f0" />
1 parent 6654070 commit 55ccd5f

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {MergedIssuesSidebarSection} from 'sentry/views/issueDetails/streamline/s
3030
import {PeopleSection} from 'sentry/views/issueDetails/streamline/sidebar/peopleSection';
3131
import {SeerSection} from 'sentry/views/issueDetails/streamline/sidebar/seerSection';
3232
import {SimilarIssuesSidebarSection} from 'sentry/views/issueDetails/streamline/sidebar/similarIssuesSidebarSection';
33+
import {SupergroupSection} from 'sentry/views/issueDetails/streamline/sidebar/supergroupSection';
3334

3435
type Props = {group: Group; project: Project; event?: Event};
3536

@@ -130,6 +131,9 @@ export function StreamlinedSidebar({group, event, project}: Props) {
130131
{issueTypeConfig.detector.enabled && (
131132
<DetectorSection group={group} project={project} />
132133
)}
134+
<ErrorBoundary mini>
135+
<SupergroupSection group={group} />
136+
</ErrorBoundary>
133137
</Side>
134138
)}
135139
</SharedTourElement>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {GroupFixture} from 'sentry-fixture/group';
2+
import {OrganizationFixture} from 'sentry-fixture/organization';
3+
4+
import {render, screen} from 'sentry-test/reactTestingLibrary';
5+
6+
import {SupergroupSection} from 'sentry/views/issueDetails/streamline/sidebar/supergroupSection';
7+
8+
describe('SupergroupSection', () => {
9+
it('renders supergroup info when issue belongs to one', async () => {
10+
const organization = OrganizationFixture({features: ['top-issues-ui']});
11+
const group = GroupFixture({id: '1'});
12+
MockApiClient.addMockResponse({
13+
url: `/organizations/${organization.slug}/seer/supergroups/by-group/`,
14+
body: {
15+
data: [
16+
{
17+
id: 10,
18+
title: 'Null pointer in auth flow',
19+
error_type: 'TypeError',
20+
code_area: 'auth/login',
21+
summary: '',
22+
group_ids: [1, 2, 3],
23+
created_at: '2024-01-01T00:00:00Z',
24+
updated_at: '2024-01-01T00:00:00Z',
25+
},
26+
],
27+
},
28+
});
29+
30+
render(<SupergroupSection group={group} />, {organization});
31+
32+
expect(await screen.findByText('TypeError')).toBeInTheDocument();
33+
expect(screen.getByText('Null pointer in auth flow')).toBeInTheDocument();
34+
expect(screen.getByText('3 issues')).toBeInTheDocument();
35+
});
36+
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {Fragment} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import InteractionStateLayer from '@sentry/scraps/interactionStateLayer';
5+
import {Flex, Stack} from '@sentry/scraps/layout';
6+
import {Text} from '@sentry/scraps/text';
7+
8+
import {useDrawer} from 'sentry/components/globalDrawer';
9+
import {IconStack} from 'sentry/icons';
10+
import {t, tn} from 'sentry/locale';
11+
import type {Group} from 'sentry/types/group';
12+
import {useOrganization} from 'sentry/utils/useOrganization';
13+
import {SidebarSectionTitle} from 'sentry/views/issueDetails/streamline/sidebar/sidebar';
14+
import {SupergroupDetailDrawer} from 'sentry/views/issueList/supergroups/supergroupDrawer';
15+
import {useSuperGroups} from 'sentry/views/issueList/supergroups/useSuperGroups';
16+
17+
interface SupergroupSectionProps {
18+
group: Group;
19+
}
20+
21+
export function SupergroupSection({group}: SupergroupSectionProps) {
22+
const organization = useOrganization();
23+
const {openDrawer} = useDrawer();
24+
const {data: lookup, isLoading} = useSuperGroups([group.id]);
25+
const supergroup = lookup[group.id];
26+
27+
if (!organization.features.includes('top-issues-ui')) {
28+
return null;
29+
}
30+
31+
if (isLoading || !supergroup) {
32+
return null;
33+
}
34+
35+
const issueCount = supergroup.group_ids.length;
36+
37+
const handleClick = () => {
38+
openDrawer(
39+
() => (
40+
<SupergroupDetailDrawer supergroup={supergroup} matchedGroupIds={[group.id]} />
41+
),
42+
{
43+
ariaLabel: t('Supergroup details'),
44+
drawerKey: 'supergroup-drawer',
45+
}
46+
);
47+
};
48+
49+
return (
50+
<div>
51+
<SidebarSectionTitle>{t('Supergroup')}</SidebarSectionTitle>
52+
<SupergroupCard onClick={handleClick} aria-label={t('Supergroup details')}>
53+
<InteractionStateLayer />
54+
<Flex gap="sm" align="start">
55+
<AccentIcon size="sm" />
56+
<Stack gap="xs" style={{overflow: 'hidden'}}>
57+
{supergroup.error_type ? (
58+
<Text size="sm" bold ellipsis>
59+
{supergroup.error_type}
60+
</Text>
61+
) : null}
62+
<Text size="sm" variant="muted" ellipsis>
63+
{supergroup.title}
64+
</Text>
65+
<Flex gap="sm" align="center">
66+
{supergroup.code_area ? (
67+
<Fragment>
68+
<Text size="xs" variant="muted" ellipsis>
69+
{supergroup.code_area}
70+
</Text>
71+
<Dot />
72+
</Fragment>
73+
) : null}
74+
<Text size="xs" variant="muted" style={{flexShrink: 0}}>
75+
{tn('%s issue', '%s issues', issueCount)}
76+
</Text>
77+
</Flex>
78+
</Stack>
79+
</Flex>
80+
</SupergroupCard>
81+
</div>
82+
);
83+
}
84+
85+
const SupergroupCard = styled('button')`
86+
position: relative;
87+
width: 100%;
88+
padding: ${p => p.theme.space.md};
89+
border: 1px solid ${p => p.theme.tokens.border.primary};
90+
border-radius: ${p => p.theme.radius.md};
91+
cursor: pointer;
92+
background: transparent;
93+
text-align: left;
94+
font: inherit;
95+
color: inherit;
96+
`;
97+
98+
const AccentIcon = styled(IconStack)`
99+
color: ${p => p.theme.tokens.graphics.accent.vibrant};
100+
flex-shrink: 0;
101+
margin-top: 2px;
102+
`;
103+
104+
const Dot = styled('div')`
105+
width: 3px;
106+
height: 3px;
107+
border-radius: 50%;
108+
background: currentcolor;
109+
flex-shrink: 0;
110+
`;

0 commit comments

Comments
 (0)