Skip to content

Commit 147bec1

Browse files
authored
feat(issues): more functionality for different team viewing in top issues (#103947)
Add an option to select teams to filter the clusters by, rather than just owned by the user. Primarily to help with debugging.
1 parent 245bf3a commit 147bec1

File tree

1 file changed

+81
-8
lines changed

1 file changed

+81
-8
lines changed

static/app/views/issueList/pages/dynamicGrouping.tsx

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Fragment, useState} from 'react';
1+
import {Fragment, useMemo, useState} from 'react';
22
import styled from '@emotion/styled';
33

44
import {Container, Flex} from '@sentry/scraps/layout';
@@ -27,9 +27,9 @@ import {useUser} from 'sentry/utils/useUser';
2727
import {useUserTeams} from 'sentry/utils/useUserTeams';
2828

2929
interface AssignedEntity {
30-
email: string | null; // unused
30+
email: string | null;
3131
id: string;
32-
name: string; // unused
32+
name: string;
3333
type: string;
3434
}
3535

@@ -207,8 +207,9 @@ function ClusterCard({
207207
function DynamicGrouping() {
208208
const organization = useOrganization();
209209
const user = useUser();
210-
const {teams} = useUserTeams();
210+
const {teams: userTeams} = useUserTeams();
211211
const [filterByAssignedToMe, setFilterByAssignedToMe] = useState(true);
212+
const [selectedTeamIds, setSelectedTeamIds] = useState<Set<string>>(new Set());
212213
const [minFixabilityScore, setMinFixabilityScore] = useState(50);
213214
const [removedClusterIds, setRemovedClusterIds] = useState<Set<number>>(new Set());
214215

@@ -222,6 +223,39 @@ function DynamicGrouping() {
222223

223224
const clusterData = topIssuesResponse?.data ?? [];
224225

226+
// Extract all unique teams from the cluster data
227+
const teamsInData = useMemo(() => {
228+
const data = topIssuesResponse?.data ?? [];
229+
const teamMap = new Map<string, {id: string; name: string}>();
230+
for (const cluster of data) {
231+
for (const entity of cluster.assignedTo ?? []) {
232+
if (entity.type === 'team' && !teamMap.has(entity.id)) {
233+
teamMap.set(entity.id, {id: entity.id, name: entity.name});
234+
}
235+
}
236+
}
237+
return Array.from(teamMap.values()).sort((a, b) => a.name.localeCompare(b.name));
238+
}, [topIssuesResponse?.data]);
239+
240+
const isTeamFilterActive = selectedTeamIds.size > 0;
241+
242+
const handleAssignedToMeChange = (checked: boolean) => {
243+
setFilterByAssignedToMe(checked);
244+
if (checked) {
245+
setSelectedTeamIds(new Set());
246+
}
247+
};
248+
249+
const handleTeamToggle = (teamId: string) => {
250+
const next = new Set(selectedTeamIds);
251+
next.has(teamId) ? next.delete(teamId) : next.add(teamId);
252+
253+
setSelectedTeamIds(next);
254+
if (next.size > 0) {
255+
setFilterByAssignedToMe(false);
256+
}
257+
};
258+
225259
const handleRemoveCluster = (clusterId: number) => {
226260
setRemovedClusterIds(prev => new Set([...prev, clusterId]));
227261
};
@@ -238,9 +272,17 @@ function DynamicGrouping() {
238272
return cluster.assignedTo.some(
239273
entity =>
240274
(entity.type === 'user' && entity.id === user.id) ||
241-
(entity.type === 'team' && teams.some(team => team.id === entity.id))
275+
(entity.type === 'team' && userTeams.some(team => team.id === entity.id))
276+
);
277+
}
278+
279+
if (isTeamFilterActive) {
280+
if (!cluster.assignedTo?.length) return false;
281+
return cluster.assignedTo.some(
282+
entity => entity.type === 'team' && selectedTeamIds.has(entity.id)
242283
);
243284
}
285+
244286
return true;
245287
})
246288
.sort((a, b) => (b.fixability_score ?? 0) - (a.fixability_score ?? 0));
@@ -300,14 +342,40 @@ function DynamicGrouping() {
300342
<Flex gap="sm" align="center">
301343
<Checkbox
302344
checked={filterByAssignedToMe}
303-
onChange={e => setFilterByAssignedToMe(e.target.checked)}
345+
onChange={e => handleAssignedToMeChange(e.target.checked)}
304346
aria-label={t('Show only issues assigned to me')}
305347
size="sm"
348+
disabled={isTeamFilterActive}
306349
/>
307-
<Text size="sm" variant="muted">
350+
<FilterLabel disabled={isTeamFilterActive}>
308351
{t('Only show issues assigned to me')}
309-
</Text>
352+
</FilterLabel>
310353
</Flex>
354+
355+
{teamsInData.length > 0 && (
356+
<Flex direction="column" gap="sm">
357+
<FilterLabel disabled={filterByAssignedToMe}>
358+
{t('Filter by teams')}
359+
</FilterLabel>
360+
<Flex direction="column" gap="xs" style={{paddingLeft: 8}}>
361+
{teamsInData.map(team => (
362+
<Flex key={team.id} gap="sm" align="center">
363+
<Checkbox
364+
checked={selectedTeamIds.has(team.id)}
365+
onChange={() => handleTeamToggle(team.id)}
366+
aria-label={t('Filter by team %s', team.name)}
367+
size="sm"
368+
disabled={filterByAssignedToMe}
369+
/>
370+
<FilterLabel disabled={filterByAssignedToMe}>
371+
#{team.name}
372+
</FilterLabel>
373+
</Flex>
374+
))}
375+
</Flex>
376+
</Flex>
377+
)}
378+
311379
<Flex gap="sm" align="center">
312380
<Text size="sm" variant="muted">
313381
{t('Minimum fixability score (%)')}
@@ -486,4 +554,9 @@ const DescriptionText = styled('p')`
486554
line-height: 1.5;
487555
`;
488556

557+
const FilterLabel = styled('span')<{disabled?: boolean}>`
558+
font-size: ${p => p.theme.fontSize.sm};
559+
color: ${p => (p.disabled ? p.theme.disabled : p.theme.subText)};
560+
`;
561+
489562
export default DynamicGrouping;

0 commit comments

Comments
 (0)