Skip to content

Commit 59fac4b

Browse files
authored
ref(top-issues): ui touchup for aggregate stats (#104381)
Back to the chips for aggregate stats <img width="506" height="41" alt="Screenshot 2025-12-04 at 1 40 27 PM" src="https://github.com/user-attachments/assets/86722108-bf9c-45e9-a4c6-c2c12302412d" />
1 parent 49e4554 commit 59fac4b

File tree

1 file changed

+80
-78
lines changed

1 file changed

+80
-78
lines changed

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

Lines changed: 80 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ import Redirect from 'sentry/components/redirect';
2626
import TimeSince from 'sentry/components/timeSince';
2727
import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
2828
import {
29+
IconCalendar,
2930
IconChevron,
31+
IconClock,
3032
IconClose,
3133
IconCopy,
3234
IconFire,
35+
IconFix,
3336
IconSeer,
3437
IconUpload,
38+
IconUser,
3539
} from 'sentry/icons';
3640
import {t, tn} from 'sentry/locale';
3741
import {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

206211
function 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

Comments
 (0)