Skip to content

Commit b873262

Browse files
committed
feat: cluster status tags
1 parent 320f61b commit b873262

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

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

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Redirect from 'sentry/components/redirect';
2727
import TimeSince from 'sentry/components/timeSince';
2828
import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
2929
import {
30+
IconArrow,
3031
IconCalendar,
3132
IconChevron,
3233
IconClock,
@@ -35,7 +36,9 @@ import {
3536
IconEllipsis,
3637
IconFire,
3738
IconFix,
39+
IconRefresh,
3840
IconSeer,
41+
IconStar,
3942
IconUpload,
4043
IconUser,
4144
} from 'sentry/icons';
@@ -190,8 +193,11 @@ function CompactIssuePreview({group}: {group: Group}) {
190193

191194
interface ClusterStats {
192195
firstSeen: string | null;
196+
hasRegressedIssues: boolean;
197+
isEscalating: boolean;
193198
isPending: boolean;
194199
lastSeen: string | null;
200+
newIssuesCount: number;
195201
totalEvents: number;
196202
totalUsers: number;
197203
}
@@ -222,6 +228,9 @@ function useClusterStats(groupIds: number[]): ClusterStats {
222228
totalUsers: 0,
223229
firstSeen: null,
224230
lastSeen: null,
231+
newIssuesCount: 0,
232+
hasRegressedIssues: false,
233+
isEscalating: false,
225234
isPending,
226235
};
227236
}
@@ -231,6 +240,19 @@ function useClusterStats(groupIds: number[]): ClusterStats {
231240
let earliestFirstSeen: Date | null = null;
232241
let latestLastSeen: Date | null = null;
233242

243+
// Calculate new issues (first seen within last week)
244+
const oneWeekAgo = new Date();
245+
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
246+
let newIssuesCount = 0;
247+
248+
// Check for regressed issues
249+
let hasRegressedIssues = false;
250+
251+
// Calculate escalation by summing event stats across all issues
252+
// We'll compare the first half of the 24h stats to the second half
253+
let firstHalfEvents = 0;
254+
let secondHalfEvents = 0;
255+
234256
for (const group of groups) {
235257
totalEvents += parseInt(group.count, 10) || 0;
236258
totalUsers += group.userCount || 0;
@@ -240,6 +262,10 @@ function useClusterStats(groupIds: number[]): ClusterStats {
240262
if (!earliestFirstSeen || firstSeenDate < earliestFirstSeen) {
241263
earliestFirstSeen = firstSeenDate;
242264
}
265+
// Check if this issue is new (first seen within last week)
266+
if (firstSeenDate >= oneWeekAgo) {
267+
newIssuesCount++;
268+
}
243269
}
244270

245271
if (group.lastSeen) {
@@ -248,13 +274,39 @@ function useClusterStats(groupIds: number[]): ClusterStats {
248274
latestLastSeen = lastSeenDate;
249275
}
250276
}
277+
278+
// Check for regressed substatus
279+
if (group.substatus === GroupSubstatus.REGRESSED) {
280+
hasRegressedIssues = true;
281+
}
282+
283+
// Aggregate 24h stats for escalation detection
284+
const stats24h = group.stats?.['24h'];
285+
if (stats24h && stats24h.length > 0) {
286+
const midpoint = Math.floor(stats24h.length / 2);
287+
for (let i = 0; i < stats24h.length; i++) {
288+
const eventCount = stats24h[i]?.[1] ?? 0;
289+
if (i < midpoint) {
290+
firstHalfEvents += eventCount;
291+
} else {
292+
secondHalfEvents += eventCount;
293+
}
294+
}
295+
}
251296
}
252297

298+
// Determine if escalating: second half has >1.5x events compared to first half
299+
// Only consider escalating if there were events in the first half (avoid division by zero)
300+
const isEscalating = firstHalfEvents > 0 && secondHalfEvents > firstHalfEvents * 1.5;
301+
253302
return {
254303
totalEvents,
255304
totalUsers,
256305
firstSeen: earliestFirstSeen?.toISOString() ?? null,
257306
lastSeen: latestLastSeen?.toISOString() ?? null,
307+
newIssuesCount,
308+
hasRegressedIssues,
309+
isEscalating,
258310
isPending,
259311
};
260312
}, [groups, isPending]);
@@ -473,6 +525,41 @@ function ClusterCard({cluster}: {cluster: ClusterSummary}) {
473525
</StatItem>
474526
)}
475527
</ClusterStats>
528+
{!clusterStats.isPending &&
529+
(clusterStats.newIssuesCount > 0 ||
530+
clusterStats.hasRegressedIssues ||
531+
clusterStats.isEscalating) && (
532+
<ClusterStatusTags>
533+
{clusterStats.newIssuesCount > 0 && (
534+
<StatusTag color="purple">
535+
<IconStar size="xs" />
536+
<Text size="xs" bold>
537+
{tn(
538+
'%s new issue this week',
539+
'%s new issues this week',
540+
clusterStats.newIssuesCount
541+
)}
542+
</Text>
543+
</StatusTag>
544+
)}
545+
{clusterStats.hasRegressedIssues && (
546+
<StatusTag color="yellow">
547+
<IconRefresh size="xs" />
548+
<Text size="xs" bold>
549+
{t('Has regressed issues')}
550+
</Text>
551+
</StatusTag>
552+
)}
553+
{clusterStats.isEscalating && (
554+
<StatusTag color="red">
555+
<IconArrow direction="up" size="xs" />
556+
<Text size="xs" bold>
557+
{t('Escalating')}
558+
</Text>
559+
</StatusTag>
560+
)}
561+
</ClusterStatusTags>
562+
)}
476563
</CardHeader>
477564

478565
<TabSection>
@@ -1109,6 +1196,45 @@ const MoreProjectsCount = styled('span')`
11091196
margin-left: ${space(0.25)};
11101197
`;
11111198

1199+
// Status tags row for new/regressed/escalating indicators
1200+
const ClusterStatusTags = styled('div')`
1201+
display: flex;
1202+
flex-wrap: wrap;
1203+
gap: ${space(1)};
1204+
margin-top: ${space(1)};
1205+
`;
1206+
1207+
const StatusTag = styled('div')<{color: 'purple' | 'yellow' | 'red'}>`
1208+
display: inline-flex;
1209+
align-items: center;
1210+
gap: ${space(0.5)};
1211+
padding: ${space(0.5)} ${space(1)};
1212+
border-radius: ${p => p.theme.borderRadius};
1213+
font-size: ${p => p.theme.fontSize.xs};
1214+
1215+
${p => {
1216+
switch (p.color) {
1217+
case 'purple':
1218+
return `
1219+
background: ${p.theme.purple100};
1220+
color: ${p.theme.purple400};
1221+
`;
1222+
case 'yellow':
1223+
return `
1224+
background: ${p.theme.yellow100};
1225+
color: ${p.theme.yellow400};
1226+
`;
1227+
case 'red':
1228+
return `
1229+
background: ${p.theme.red100};
1230+
color: ${p.theme.red400};
1231+
`;
1232+
default:
1233+
return '';
1234+
}
1235+
}}
1236+
`;
1237+
11121238
// Tab section for Summary / Preview Issues
11131239
const TabSection = styled('div')``;
11141240

0 commit comments

Comments
 (0)