1- import { Fragment , useState } from 'react' ;
1+ import { Fragment , useMemo , useState } from 'react' ;
22import styled from '@emotion/styled' ;
33
44import { Container , Flex } from '@sentry/scraps/layout' ;
@@ -27,9 +27,9 @@ import {useUser} from 'sentry/utils/useUser';
2727import { useUserTeams } from 'sentry/utils/useUserTeams' ;
2828
2929interface 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({
207207function 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+
489562export default DynamicGrouping ;
0 commit comments