Skip to content

Commit 3980a0f

Browse files
authored
feat(ui): default suite group-by to first available label (#214)
## Summary - Suite detail page's "Group by" selector now auto-defaults to the first available label when a suite has label metadata (previously always defaulted to "None"). - Explicit "None" is still selectable and is persisted in the URL as `groupBy=none` so the opt-out survives navigation. - `effectiveGroupBy` drives `applyGrouping`, the button active states, and the `RunsHeatmap` compare callbacks; raw `groupBy` is still forwarded unchanged across URL transitions. ## Test plan - [ ] Open a suite with labeled runs → first label is selected by default in "Group by". - [ ] Click "None" → grouping disabled, URL shows `groupBy=none`, state persists after reload and tab/filter changes. - [ ] Click a label or "instance id" → selection reflected in URL and heatmap grouping. - [ ] Open a suite with no labels → selector remains hidden / no regression.
1 parent 6feed8e commit 3980a0f

1 file changed

Lines changed: 30 additions & 21 deletions

File tree

ui/src/pages/SuiteDetailPage.tsx

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -436,22 +436,31 @@ export function SuiteDetailPage() {
436436
return Array.from(keys).sort()
437437
}, [suiteRunsAll])
438438

439+
// Effective group-by: when groupBy is unset in the URL, auto-default to the
440+
// first available label key. The sentinel 'none' means the user explicitly
441+
// chose no grouping.
442+
const effectiveGroupBy = useMemo(() => {
443+
if (groupBy === 'none') return undefined
444+
if (groupBy !== undefined) return groupBy
445+
return groupByLabelKeys[0]
446+
}, [groupBy, groupByLabelKeys])
447+
439448
// Apply grouping: transforms client name to include the group suffix
440449
const applyGrouping = useCallback((runs: IndexEntry[]): IndexEntry[] => {
441-
if (!groupBy) return runs
450+
if (!effectiveGroupBy) return runs
442451
return runs.map((run) => {
443452
let suffix: string
444-
if (groupBy === 'instance_id') {
453+
if (effectiveGroupBy === 'instance_id') {
445454
suffix = run.instance.id
446455
} else {
447-
suffix = run.metadata?.[groupBy] ?? '(none)'
456+
suffix = run.metadata?.[effectiveGroupBy] ?? '(none)'
448457
}
449458
return {
450459
...run,
451460
instance: { ...run.instance, client: `${run.instance.client} / ${suffix}` },
452461
}
453462
})
454-
}, [groupBy])
463+
}, [effectiveGroupBy])
455464

456465
const groupedRunsAll = useMemo(() => applyGrouping(suiteRunsAll), [suiteRunsAll, applyGrouping])
457466

@@ -961,9 +970,9 @@ export function SuiteDetailPage() {
961970
<span className="text-xs/5 font-medium text-gray-700 sm:text-sm/6 dark:text-gray-300">Group by:</span>
962971
<div className="flex items-center gap-1">
963972
<button
964-
onClick={() => handleGroupByChange(undefined)}
973+
onClick={() => handleGroupByChange('none')}
965974
className={`rounded-xs px-2 py-0.5 text-xs font-medium transition-colors sm:px-2.5 sm:py-1 ${
966-
!groupBy
975+
!effectiveGroupBy
967976
? 'bg-gray-800 text-white dark:bg-gray-200 dark:text-gray-900'
968977
: 'bg-gray-100 text-gray-500 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600'
969978
}`}
@@ -975,7 +984,7 @@ export function SuiteDetailPage() {
975984
key={key}
976985
onClick={() => handleGroupByChange(key)}
977986
className={`rounded-xs px-2 py-0.5 text-xs font-medium transition-colors sm:px-2.5 sm:py-1 ${
978-
groupBy === key
987+
effectiveGroupBy === key
979988
? 'bg-gray-800 text-white dark:bg-gray-200 dark:text-gray-900'
980989
: 'bg-gray-100 text-gray-500 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600'
981990
}`}
@@ -986,7 +995,7 @@ export function SuiteDetailPage() {
986995
<button
987996
onClick={() => handleGroupByChange('instance_id')}
988997
className={`rounded-xs px-2 py-0.5 text-xs font-medium transition-colors sm:px-2.5 sm:py-1 ${
989-
groupBy === 'instance_id'
998+
effectiveGroupBy === 'instance_id'
990999
? 'bg-gray-800 text-white dark:bg-gray-200 dark:text-gray-900'
9911000
: 'bg-gray-100 text-gray-500 hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-600'
9921001
}`}
@@ -1046,8 +1055,8 @@ export function SuiteDetailPage() {
10461055
<div className="border-t border-gray-200 p-3 sm:p-4 dark:border-gray-700">
10471056
<RunsHeatmap
10481057
runs={suiteRunsAll}
1049-
groupBy={groupBy}
1050-
onCompareGroup={groupBy ? (groupRuns) => {
1058+
groupBy={effectiveGroupBy}
1059+
onCompareGroup={effectiveGroupBy ? (groupRuns) => {
10511060
const sorted = [...groupRuns].sort((a, b) => b.timestamp - a.timestamp)
10521061
const seen = new Set<string>()
10531062
const ids: string[] = []
@@ -1066,7 +1075,7 @@ export function SuiteDetailPage() {
10661075
navigate({ to: '/compare', search: { runs: ids.join(',') } })
10671076
}
10681077
} : undefined}
1069-
onCompareClientAcrossGroups={groupBy ? (client) => {
1078+
onCompareClientAcrossGroups={effectiveGroupBy ? (client) => {
10701079
// Find the latest successful run for this client in each group
10711080
const sorted = [...suiteRunsAll]
10721081
.filter((r) => r.instance.client === client)
@@ -1076,9 +1085,9 @@ export function SuiteDetailPage() {
10761085
for (const run of sorted) {
10771086
// Skip live runs — can't compare in-progress runs.
10781087
if (run.status === 'running') continue
1079-
const groupValue = groupBy === 'instance_id'
1088+
const groupValue = effectiveGroupBy === 'instance_id'
10801089
? run.instance.id
1081-
: (run.metadata?.[groupBy] ?? '(none)')
1090+
: (run.metadata?.[effectiveGroupBy] ?? '(none)')
10821091
if (seenGroups.has(groupValue)) continue
10831092
if (run.tests.tests_total > 0 && run.tests.tests_passed === run.tests.tests_total) {
10841093
seenGroups.add(groupValue)
@@ -1087,28 +1096,28 @@ export function SuiteDetailPage() {
10871096
if (ids.length >= MAX_COMPARE_RUNS) break
10881097
}
10891098
if (ids.length >= MIN_COMPARE_RUNS) {
1090-
const labels = groupBy === 'instance_id' ? 'instance-id' : `label:${groupBy}`
1099+
const labels = effectiveGroupBy === 'instance_id' ? 'instance-id' : `label:${effectiveGroupBy}`
10911100
navigate({ to: '/compare', search: { runs: ids.join(','), labels } })
10921101
}
10931102
} : undefined}
1094-
onGroupCompareGroup={groupBy ? (groupLabel, groupClients) => {
1095-
const labelFilter = groupBy !== 'instance_id' ? `${groupBy}=${groupLabel}` : ''
1103+
onGroupCompareGroup={effectiveGroupBy ? (groupLabel, groupClients) => {
1104+
const labelFilter = effectiveGroupBy !== 'instance_id' ? `${effectiveGroupBy}=${groupLabel}` : ''
10961105
const groups = groupClients.map((c) => `${c}:${labelFilter}`).join(';')
10971106
navigate({ to: '/compare/groups', search: { suite: suiteHash, groups } as Record<string, string> })
10981107
} : undefined}
1099-
onGroupCompareClientAcrossGroups={groupBy ? (client) => {
1108+
onGroupCompareClientAcrossGroups={effectiveGroupBy ? (client) => {
11001109
// Build one group per label-value for this client.
11011110
const labelValues = new Set<string>()
11021111
for (const run of suiteRunsAll) {
11031112
if (run.instance.client !== client) continue
1104-
const val = groupBy === 'instance_id'
1113+
const val = effectiveGroupBy === 'instance_id'
11051114
? run.instance.id
1106-
: (run.metadata?.[groupBy] ?? '')
1115+
: (run.metadata?.[effectiveGroupBy] ?? '')
11071116
if (val) labelValues.add(val)
11081117
}
11091118
const groups = [...labelValues].sort().map((val) => {
1110-
if (groupBy === 'instance_id') return `${client}:`
1111-
return `${client}:${groupBy}=${val}`
1119+
if (effectiveGroupBy === 'instance_id') return `${client}:`
1120+
return `${client}:${effectiveGroupBy}=${val}`
11121121
}).join(';')
11131122
navigate({ to: '/compare/groups', search: { suite: suiteHash, groups } as Record<string, string> })
11141123
} : undefined}

0 commit comments

Comments
 (0)