@@ -26,12 +26,16 @@ import Redirect from 'sentry/components/redirect';
2626import TimeSince from 'sentry/components/timeSince' ;
2727import { ALL_ACCESS_PROJECTS } from 'sentry/constants/pageFilters' ;
2828import {
29+ IconCalendar ,
2930 IconChevron ,
31+ IconClock ,
3032 IconClose ,
3133 IconCopy ,
3234 IconFire ,
35+ IconFix ,
3336 IconSeer ,
3437 IconUpload ,
38+ IconUser ,
3539} from 'sentry/icons' ;
3640import { t , tn } from 'sentry/locale' ;
3741import { space } from 'sentry/styles/space' ;
@@ -201,6 +205,7 @@ interface ClusterStats {
201205 isPending : boolean ;
202206 lastSeen : string | null ;
203207 totalEvents : number ;
208+ totalUsers : number ;
204209}
205210
206211function useClusterStats ( groupIds : number [ ] ) : ClusterStats {
@@ -226,18 +231,21 @@ function useClusterStats(groupIds: number[]): ClusterStats {
226231 if ( isPending || ! groups || groups . length === 0 ) {
227232 return {
228233 totalEvents : 0 ,
234+ totalUsers : 0 ,
229235 firstSeen : null ,
230236 lastSeen : null ,
231237 isPending,
232238 } ;
233239 }
234240
235241 let totalEvents = 0 ;
242+ let totalUsers = 0 ;
236243 let earliestFirstSeen : Date | null = null ;
237244 let latestLastSeen : Date | null = null ;
238245
239246 for ( const group of groups ) {
240247 totalEvents += parseInt ( group . count , 10 ) || 0 ;
248+ totalUsers += group . userCount || 0 ;
241249
242250 if ( group . firstSeen ) {
243251 const firstSeenDate = new Date ( group . firstSeen ) ;
@@ -256,6 +264,7 @@ function useClusterStats(groupIds: number[]): ClusterStats {
256264
257265 return {
258266 totalEvents,
267+ totalUsers,
259268 firstSeen : earliestFirstSeen ?. toISOString ( ) ?? null ,
260269 lastSeen : latestLastSeen ?. toISOString ( ) ?? null ,
261270 isPending,
@@ -394,51 +403,71 @@ function ClusterCard({
394403 </ CardHeader >
395404
396405 { /* Zone 2: Stats (Secondary Context) */ }
397- < StatsSection >
398- < PrimaryStats >
399- < EventsMetric >
400- < IconFire size = "sm" />
401- { clusterStats . isPending ? (
402- < Text size = "md" variant = "muted" >
403- –
404- </ Text >
405- ) : (
406- < EventsCount > { clusterStats . totalEvents . toLocaleString ( ) } </ EventsCount >
407- ) }
408- < Text size = "sm" variant = "muted" >
406+ < ClusterStatsBar >
407+ { cluster . fixability_score !== null && cluster . fixability_score !== undefined && (
408+ < StatItem >
409+ < IconFix size = "xs" color = "gray300" />
410+ < Text size = "xs" >
411+ < Text size = "xs" bold as = "span" >
412+ { Math . round ( cluster . fixability_score * 100 ) } %
413+ </ Text > { ' ' }
414+ { t ( 'relevance' ) }
415+ </ Text >
416+ </ StatItem >
417+ ) }
418+ < StatItem >
419+ < IconFire size = "xs" color = "gray300" />
420+ { clusterStats . isPending ? (
421+ < Text size = "xs" variant = "muted" >
422+ –
423+ </ Text >
424+ ) : (
425+ < Text size = "xs" >
426+ < Text size = "xs" bold as = "span" >
427+ { clusterStats . totalEvents . toLocaleString ( ) }
428+ </ Text > { ' ' }
409429 { tn ( 'event' , 'events' , clusterStats . totalEvents ) }
410430 </ Text >
411- </ EventsMetric >
412- </ PrimaryStats >
413- < SecondaryStats >
414- { ! clusterStats . isPending && clusterStats . lastSeen && (
415- < SecondaryStatItem >
416- < Text size = "xs" variant = "muted" >
417- { t ( 'Last seen' ) }
418- </ Text >
419- < TimeSince
420- tooltipPrefix = { t ( 'Last Seen' ) }
421- date = { clusterStats . lastSeen }
422- suffix = { t ( 'ago' ) }
423- unitStyle = "short"
424- />
425- </ SecondaryStatItem >
426431 ) }
427- { ! clusterStats . isPending && clusterStats . firstSeen && (
428- < SecondaryStatItem >
429- < Text size = "xs" variant = "muted" >
430- { t ( 'Age' ) }
431- </ Text >
432- < TimeSince
433- tooltipPrefix = { t ( 'First Seen' ) }
434- date = { clusterStats . firstSeen }
435- suffix = { t ( 'old' ) }
436- unitStyle = "short"
437- />
438- </ SecondaryStatItem >
432+ </ StatItem >
433+ < StatItem >
434+ < IconUser size = "xs" color = "gray300" />
435+ { clusterStats . isPending ? (
436+ < Text size = "xs" variant = "muted" >
437+ –
438+ </ Text >
439+ ) : (
440+ < Text size = "xs" >
441+ < Text size = "xs" bold as = "span" >
442+ { clusterStats . totalUsers . toLocaleString ( ) }
443+ </ Text > { ' ' }
444+ { tn ( 'user' , 'users' , clusterStats . totalUsers ) }
445+ </ Text >
439446 ) }
440- </ SecondaryStats >
441- </ StatsSection >
447+ </ StatItem >
448+ { ! clusterStats . isPending && clusterStats . lastSeen && (
449+ < StatItem >
450+ < IconClock size = "xs" color = "gray300" />
451+ < TimeSince
452+ tooltipPrefix = { t ( 'Last Seen' ) }
453+ date = { clusterStats . lastSeen }
454+ suffix = { t ( 'ago' ) }
455+ unitStyle = "short"
456+ />
457+ </ StatItem >
458+ ) }
459+ { ! clusterStats . isPending && clusterStats . firstSeen && (
460+ < StatItem >
461+ < IconCalendar size = "xs" color = "gray300" />
462+ < TimeSince
463+ tooltipPrefix = { t ( 'First Seen' ) }
464+ date = { clusterStats . firstSeen }
465+ suffix = { t ( 'old' ) }
466+ unitStyle = "short"
467+ />
468+ </ StatItem >
469+ ) }
470+ </ ClusterStatsBar >
442471
443472 { /* Zone 3: Nested Issues (Detail Content) */ }
444473 < IssuesSection >
@@ -1023,50 +1052,23 @@ const ClusterTitle = styled('h3')`
10231052 word-break: break-word;
10241053` ;
10251054
1026- // Zone 2: Stats section with visual hierarchy
1027- const StatsSection = styled ( 'div' ) `
1028- padding: ${ space ( 2 ) } ${ space ( 3 ) } ;
1029- background: ${ p => p . theme . backgroundSecondary } ;
1030- border-top: 1px solid ${ p => p . theme . innerBorder } ;
1031- border-bottom: 1px solid ${ p => p . theme . innerBorder } ;
1055+ // Horizontal stats bar below header
1056+ const ClusterStatsBar = styled ( 'div' ) `
10321057 display: flex;
1033- justify-content: space-between;
1034- align-items: center;
1035- gap: ${ space ( 2 ) } ;
10361058 flex-wrap: wrap;
1037- ` ;
1038-
1039- const PrimaryStats = styled ( 'div' ) `
1040- display: flex;
10411059 align-items: center;
1042- gap: ${ space ( 3 ) } ;
1060+ gap: ${ space ( 2 ) } ;
1061+ padding: ${ space ( 1.5 ) } ${ space ( 3 ) } ;
1062+ border-top: 1px solid ${ p => p . theme . innerBorder } ;
1063+ border-bottom: 1px solid ${ p => p . theme . innerBorder } ;
1064+ font-size: ${ p => p . theme . fontSize . sm } ;
1065+ color: ${ p => p . theme . subText } ;
10431066` ;
10441067
1045- const EventsMetric = styled ( 'div' ) `
1068+ const StatItem = styled ( 'div' ) `
10461069 display: flex;
10471070 align-items: center;
1048- gap: ${ space ( 1 ) } ;
1049- color: ${ p => p . theme . red300 } ;
1050- ` ;
1051-
1052- const EventsCount = styled ( 'span' ) `
1053- font-size: ${ p => p . theme . fontSize . xl } ;
1054- font-weight: 700;
1055- color: ${ p => p . theme . textColor } ;
1056- font-variant-numeric: tabular-nums;
1057- ` ;
1058-
1059- const SecondaryStats = styled ( 'div' ) `
1060- display: flex;
1061- gap: ${ space ( 3 ) } ;
1062- ` ;
1063-
1064- const SecondaryStatItem = styled ( 'div' ) `
1065- display: flex;
1066- flex-direction: column;
1067- gap: ${ space ( 0.25 ) } ;
1068- font-size: ${ p => p . theme . fontSize . sm } ;
1069- color: ${ p => p . theme . textColor } ;
1071+ gap: ${ space ( 0.5 ) } ;
10701072` ;
10711073
10721074// Zone 3: Issues list with clear containment
0 commit comments