diff --git a/static/app/views/issueList/supergroups/supergroupDrawer.tsx b/static/app/views/issueList/supergroups/supergroupDrawer.tsx
index 8e5abada896b48..92f0c58708b50a 100644
--- a/static/app/views/issueList/supergroups/supergroupDrawer.tsx
+++ b/static/app/views/issueList/supergroups/supergroupDrawer.tsx
@@ -51,6 +51,7 @@ import {
useIssueSelectionSummary,
} from 'sentry/views/issueList/issueSelectionContext';
import {SupergroupFeedback} from 'sentry/views/issueList/supergroups/supergroupFeedback';
+import {SupergroupTagPreview} from 'sentry/views/issueList/supergroups/supergroupTagPreview';
import type {SupergroupDetail} from 'sentry/views/issueList/supergroups/types';
import type {IssueUpdateData} from 'sentry/views/issueList/types';
@@ -140,6 +141,8 @@ export function SupergroupDetailDrawer({
+
+
{supergroup.group_ids.length > 0 && (
+ color(theme.chart.getColorPalette(4).at(index)).alpha(0.8).toString();
+
+export function SupergroupTagPreview({groupIds}: {groupIds: number[]}) {
+ const organization = useOrganization();
+ const theme = useTheme();
+
+ const limitedGroupIds = useMemo(
+ () => groupIds.slice(0, MAX_GROUPS_FOR_TAGS),
+ [groupIds]
+ );
+
+ const tagResults = useApiQueries(
+ limitedGroupIds.map(groupId => [
+ getApiUrl('/organizations/$organizationIdOrSlug/issues/$issueId/tags/', {
+ path: {organizationIdOrSlug: organization.slug, issueId: String(groupId)},
+ }),
+ {query: {limit: 4}},
+ ]),
+ {staleTime: 30_000, enabled: limitedGroupIds.length > 0}
+ );
+
+ const isPending = tagResults.some(r => r.isPending);
+
+ const tagsToShow = useMemo(() => {
+ const tagMap = new Map<
+ string,
+ {totalValues: number; valueMap: Map}
+ >();
+
+ for (const result of tagResults) {
+ if (!result.data) {
+ continue;
+ }
+ for (const tag of result.data) {
+ let entry = tagMap.get(tag.key);
+ if (!entry) {
+ entry = {totalValues: 0, valueMap: new Map()};
+ tagMap.set(tag.key, entry);
+ }
+ entry.totalValues += tag.totalValues;
+ for (const val of tag.topValues) {
+ const existing = entry.valueMap.get(val.value);
+ if (existing) {
+ existing.count += val.count;
+ } else {
+ entry.valueMap.set(val.value, {name: val.name, count: val.count});
+ }
+ }
+ }
+ }
+
+ const ordered: Array<{
+ key: string;
+ topValues: Array<{count: number; name: string; value: string}>;
+ totalValues: number;
+ }> = [];
+
+ for (const key of PRIORITY_TAGS) {
+ const entry = tagMap.get(key);
+ if (!entry || entry.valueMap.size === 0) {
+ continue;
+ }
+ const topValues = [...entry.valueMap.entries()]
+ .map(([value, {name, count}]) => ({value, name, count}))
+ .sort((a, b) => b.count - a.count)
+ .slice(0, 3);
+ ordered.push({key, totalValues: entry.totalValues, topValues});
+ }
+
+ return ordered.slice(0, 4);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isPending, tagResults.length]);
+
+ if (isPending) {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ if (tagsToShow.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {tagsToShow.map(tag => {
+ const topValue = tag.topValues[0];
+ const topPct =
+ topValue && tag.totalValues > 0
+ ? (topValue.count / tag.totalValues) * 100
+ : 0;
+ const topPctStr = topPct < 0.5 ? '<1%' : `${Math.round(topPct)}%`;
+
+ const segments = tag.topValues.map((val, idx) => ({
+ name: val.name || t('(empty)'),
+ pct: tag.totalValues > 0 ? (val.count / tag.totalValues) * 100 : 0,
+ count: val.count,
+ color: tagBarColor(idx, theme),
+ }));
+
+ const totalVisible = segments.reduce((sum, s) => sum + s.count, 0);
+ const hasOther = totalVisible < tag.totalValues;
+ const otherPct = 100 - segments.reduce((sum, s) => sum + Math.round(s.pct), 0);
+
+ return (
+
+ {tag.key}
+
+ {segments.map((seg, idx) => (
+
+
+ {seg.name}
+
+ {seg.pct < 0.5 ? '<1%' : `${Math.round(seg.pct)}%`}
+
+
+ ))}
+ {hasOther && (
+
+
+ {t('Other')}
+
+ {otherPct < 0.5 ? '<1%' : `${Math.round(otherPct)}%`}
+
+
+ )}
+
+
+ }
+ >
+
+
+ {tag.key}
+
+
+ {segments.map((seg, idx) => (
+
+ ))}
+
+
+ {topPctStr}
+
+ {topValue?.name || t('(empty)')}
+
+
+ );
+ })}
+
+
+ );
+}
+
+const TagPreviewGrid = styled('div')`
+ display: grid;
+ grid-template-columns: auto 80px min-content 1fr;
+ gap: 1px;
+ column-gap: ${p => p.theme.space.xs};
+ font-size: ${p => p.theme.font.size.sm};
+`;
+
+const TagPreviewRow = styled('div')`
+ display: grid;
+ grid-template-columns: subgrid;
+ grid-column: 1 / -1;
+ align-items: center;
+ padding: ${p => p.theme.space['2xs']} ${p => p.theme.space.sm};
+ margin: 0 -${p => p.theme.space.sm};
+ border-radius: ${p => p.theme.radius.md};
+
+ &:hover {
+ background: ${p => p.theme.tokens.background.tertiary};
+ }
+`;
+
+const TagSegmentedBar = styled('div')`
+ display: flex;
+ height: 8px;
+ width: 100%;
+ border-radius: 3px;
+ overflow: hidden;
+ /* eslint-disable-next-line @sentry/scraps/use-semantic-token */
+ box-shadow: inset 0 0 0 1px ${p => p.theme.tokens.border.transparent.neutral.muted};
+ background: ${p => color(p.theme.colors.gray400).alpha(0.1).toString()};
+`;
+
+const TagBarSegment = styled('div')`
+ height: 100%;
+ min-width: 2px;
+`;
+
+const TagTooltipLegend = styled('div')`
+ padding: ${p => p.theme.space.xs} ${p => p.theme.space.md};
+`;
+
+const TagLegendTitle = styled('div')`
+ font-weight: 600;
+ margin-bottom: ${p => p.theme.space.sm};
+`;
+
+const TagLegendGrid = styled('div')`
+ display: grid;
+ grid-template-columns: min-content auto min-content;
+ gap: ${p => p.theme.space.xs} ${p => p.theme.space.md};
+ align-items: center;
+ text-align: left;
+`;
+
+const TagLegendDot = styled('div')`
+ width: 10px;
+ height: 10px;
+ border-radius: 100%;
+`;
+
+const TagLegendPct = styled('span')`
+ font-variant-numeric: tabular-nums;
+ text-align: right;
+ white-space: nowrap;
+`;