Skip to content

Commit 7a9c0f2

Browse files
feat(inference): bring back multiselect combo box, fix spacing
1 parent 971d4c1 commit 7a9c0f2

9 files changed

Lines changed: 275 additions & 304 deletions

File tree

packages/app/cypress/component/inference-chart-controls.cy.tsx

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('GpuComparisonCard', () => {
5656
{ value: 'h200_sglang', label: 'H200 SGLang' },
5757
];
5858

59-
it('renders two required GPU slots and an Add GPU button; date range disabled', () => {
59+
it('renders GPU multiselect; date range disabled until a GPU is selected', () => {
6060
mountWithProviders(<GpuComparisonCard />, {
6161
inference: {
6262
selectedGPUs: [],
@@ -66,11 +66,16 @@ describe('GpuComparisonCard', () => {
6666

6767
cy.get('[data-testid="gpu-comparison-card"]').should('be.visible');
6868
cy.contains('GPU Comparison').should('be.visible');
69-
cy.get('[data-testid="gpu-comparison-select-1"]').should('be.visible');
70-
cy.get('[data-testid="gpu-comparison-select-2"]').should('be.visible');
71-
cy.get('[data-testid="gpu-comparison-select-3"]').should('not.exist');
72-
cy.get('[data-testid="gpu-comparison-select-4"]').should('not.exist');
73-
cy.get('[data-testid="gpu-comparison-add-slot"]').should('be.visible');
69+
cy.contains('Select one or more GPUs for date range comparison.').should('not.exist');
70+
cy.get('[data-testid="gpu-comparison-expand-toggle"]').should(
71+
'have.attr',
72+
'aria-expanded',
73+
'false',
74+
);
75+
cy.get('[data-testid="gpu-comparison-expand-toggle"]').click();
76+
cy.contains('Select one or more GPUs for date range comparison.').should('be.visible');
77+
cy.get('[data-testid="gpu-multiselect"]').should('be.visible');
78+
cy.get('[data-testid="gpu-multiselect-trigger"]').should('be.visible');
7479
cy.contains('Comparison Date Range').should('be.visible');
7580
cy.get('#gpu-comparison-date-picker').should('be.disabled');
7681
cy.get('[data-testid="date-range-shortcuts"]').should('be.visible');
@@ -79,6 +84,20 @@ describe('GpuComparisonCard', () => {
7984
cy.get('[data-testid="date-shortcut-last-30-days"]').should('be.disabled');
8085
});
8186

87+
it('enables date range picker and shortcuts when one GPU is selected', () => {
88+
mountWithProviders(<GpuComparisonCard />, {
89+
inference: {
90+
selectedGPUs: ['h100_sglang'],
91+
selectedDateRange: { startDate: '', endDate: '' },
92+
availableGPUs: gpuOptions,
93+
dateRangeAvailableDates: ['2025-10-05', '2025-11-01', '2025-12-01'],
94+
},
95+
});
96+
97+
cy.get('#gpu-comparison-date-picker').should('not.be.disabled');
98+
cy.get('[data-testid="date-shortcut-all-time"]').should('not.be.disabled');
99+
});
100+
82101
it('shows enabled shortcut buttons that call setSelectedDateRange when two GPUs are selected', () => {
83102
mountWithProviders(<GpuComparisonCard />, {
84103
inference: {
@@ -101,7 +120,7 @@ describe('GpuComparisonCard', () => {
101120
cy.get('@setSelectedDateRange').should('have.been.calledOnce');
102121
});
103122

104-
it('shows slot 3 after clicking Add GPU when two GPUs are selected', () => {
123+
it('selecting a third GPU from the multiselect calls setSelectedGPUs with three GPUs', () => {
105124
mountWithProviders(<GpuComparisonCard />, {
106125
inference: {
107126
selectedGPUs: ['h100_sglang', 'b200_sglang'],
@@ -111,29 +130,32 @@ describe('GpuComparisonCard', () => {
111130
});
112131

113132
cy.get('#gpu-comparison-date-picker').should('not.be.disabled');
114-
cy.get('[data-testid="gpu-comparison-select-3"]').should('not.exist');
115-
cy.get('[data-testid="gpu-comparison-add-slot"]').click();
116-
cy.get('[data-testid="gpu-comparison-select-3"]').should('be.visible');
117-
cy.get('[data-testid="gpu-comparison-select-4"]').should('not.exist');
118-
cy.get('[data-testid="gpu-comparison-add-slot"]').should('be.visible');
133+
cy.get('[data-testid="gpu-multiselect-trigger"]').click();
134+
cy.contains('[role="option"]', 'MI300X SGLang').click();
135+
cy.get('@setSelectedGPUs').should(
136+
'have.been.calledWith',
137+
Cypress.sinon.match((v: string[]) => v.length === 3 && v.includes('mi300x_sglang')),
138+
);
119139
});
120140

121-
it('shows all four slots when mounted with three GPUs and Add GPU clicked', () => {
141+
it('shows max-selection summary when four GPUs are selected', () => {
122142
mountWithProviders(<GpuComparisonCard />, {
123143
inference: {
124-
selectedGPUs: ['h100_sglang', 'b200_sglang', 'mi300x_sglang'],
144+
selectedGPUs: ['h100_sglang', 'b200_sglang', 'mi300x_sglang', 'h200_sglang'],
125145
selectedDateRange: { startDate: '', endDate: '' },
126146
availableGPUs: gpuOptions,
127147
},
128148
});
129149

130-
cy.get('[data-testid="gpu-comparison-select-3"]').should('be.visible');
131-
cy.get('[data-testid="gpu-comparison-add-slot"]').click();
132-
cy.get('[data-testid="gpu-comparison-select-4"]').should('be.visible');
133-
cy.get('[data-testid="gpu-comparison-add-slot"]').should('not.exist');
150+
// Wait for card expansion, then open the dropdown.
151+
// With 4 chips the center of the trigger may land on a chip's remove
152+
// button (which stopPropagates), so target the chevron icon instead.
153+
cy.get('[data-testid="gpu-multiselect-trigger"]').should('be.visible');
154+
cy.get('[data-testid="gpu-multiselect-trigger"] svg').last().click();
155+
cy.contains('4 / 4 selected').should('be.visible');
134156
});
135157

136-
it('calls setSelectedGPUs without the removed GPU when its X button is clicked', () => {
158+
it('calls setSelectedGPUs without the removed GPU when a chip remove control is clicked', () => {
137159
mountWithProviders(<GpuComparisonCard />, {
138160
inference: {
139161
selectedGPUs: ['h100_sglang', 'b200_sglang', 'mi300x_sglang'],
@@ -142,8 +164,7 @@ describe('GpuComparisonCard', () => {
142164
},
143165
});
144166

145-
cy.get('[data-testid="gpu-comparison-select-3"]').should('be.visible');
146-
cy.get('[data-testid="gpu-comparison-clear-3"]').click();
167+
cy.get('[aria-label="Remove MI300X SGLang"]').click();
147168
cy.get('@setSelectedGPUs').should(
148169
'have.been.calledWith',
149170
Cypress.sinon.match((v: string[]) => v.length === 2 && !v.includes('mi300x_sglang')),

packages/app/cypress/e2e/inference-chart.cy.ts

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/** Opens dropdown without hitting chip remove controls (they stopPropagation). */
2+
function openGpuMultiselect() {
3+
cy.get('[data-testid="gpu-multiselect-trigger"]').find('svg').last().click();
4+
}
5+
16
describe('Inference Chart', () => {
27
before(() => {
38
cy.window().then((win) => {
@@ -47,7 +52,9 @@ describe('Inference Chart', () => {
4752
});
4853

4954
it('shows the sidebar legend for GPU types', () => {
50-
cy.get('.sidebar-legend').should('be.visible');
55+
// GpuComparisonCard sits above charts; first chart legend may be below the fold.
56+
cy.get('.sidebar-legend').first().scrollIntoView();
57+
cy.get('.sidebar-legend').first().should('be.visible');
5158
});
5259
});
5360

@@ -61,34 +68,69 @@ describe('GPU Comparison Card', () => {
6168
cy.get('[data-testid="gpu-comparison-card"]').should('be.visible');
6269
});
6370

64-
it('renders the GPU comparison card with two slot selectors', () => {
65-
cy.get('[data-testid="gpu-comparison-select-1"]').should('be.visible');
66-
cy.get('[data-testid="gpu-comparison-select-2"]').should('be.visible');
67-
cy.get('[data-testid="gpu-comparison-select-3"]').should('not.exist');
71+
it('starts collapsed; expanding shows GPU comparison controls', () => {
72+
cy.contains('Select one or more GPUs for date range comparison.').should('not.exist');
73+
cy.get('[data-testid="gpu-comparison-expand-toggle"]').should(
74+
'have.attr',
75+
'aria-expanded',
76+
'false',
77+
);
78+
cy.get('[data-testid="gpu-comparison-expand-toggle"]').click();
79+
cy.get('[data-testid="gpu-comparison-expand-toggle"]').should(
80+
'have.attr',
81+
'aria-expanded',
82+
'true',
83+
);
84+
cy.contains('Select one or more GPUs for date range comparison.').should('be.visible');
85+
cy.get('[data-testid="gpu-multiselect-trigger"]').should('be.visible');
6886
});
6987

70-
it('shows date range shortcuts that are disabled until two GPUs are selected', () => {
71-
cy.get('[data-testid="date-range-shortcuts"]').should('be.visible');
72-
cy.get('[data-testid="date-shortcut-all-time"]').should('be.disabled');
73-
});
88+
describe('when expanded', () => {
89+
beforeEach(() => {
90+
cy.get('[data-testid="gpu-comparison-expand-toggle"]').click();
91+
});
7492

75-
it('selects two GPUs and verifies date range auto-defaults', () => {
76-
cy.get('[data-testid="gpu-comparison-select-1"]').click();
77-
cy.get('[role="option"]').first().click();
93+
it('renders the GPU comparison card with a single GPU multiselect', () => {
94+
cy.get('[data-testid="gpu-multiselect"]').should('be.visible');
95+
cy.get('[data-testid="gpu-multiselect-trigger"]').should('be.visible');
96+
});
7897

79-
// Pick the second available option (eq(1)) so the two GPUs come from
80-
// different hardware families with distinct dates — picking .first()
81-
// can land on the same-base MTP variant that shares a single date,
82-
// leaving dateRangeAvailableDates < 2 and "All Time" still disabled.
83-
cy.get('[data-testid="gpu-comparison-select-2"]').click();
84-
cy.get('[role="option"]').eq(1).click();
98+
it('shows date range shortcuts that are disabled until a GPU is selected', () => {
99+
cy.get('[data-testid="date-range-shortcuts"]').should('be.visible');
100+
cy.get('[data-testid="date-shortcut-all-time"]').should('be.disabled');
101+
});
85102

86-
cy.get('[data-testid="date-shortcut-all-time"]').should('not.be.disabled');
87-
cy.get('#gpu-comparison-date-picker').should('not.be.disabled');
88-
});
103+
it('selects one GPU and verifies date range controls unlock', () => {
104+
openGpuMultiselect();
105+
cy.get('[role="option"]').first().click();
89106

90-
it('Add GPU button reveals a third slot', () => {
91-
cy.get('[data-testid="gpu-comparison-add-slot"]').should('be.visible').click();
92-
cy.get('[data-testid="gpu-comparison-select-3"]').should('be.visible');
107+
// "All Time" stays disabled when the selected GPU has only one date in fixtures.
108+
cy.get('#gpu-comparison-date-picker').should('not.be.disabled');
109+
});
110+
111+
it('selects two GPUs and verifies date range auto-defaults', () => {
112+
openGpuMultiselect();
113+
cy.get('[role="option"]').first().click();
114+
115+
// Pick eq(2) so the second GPU is a different family (e.g. b300) with a
116+
// distinct date — eq(1) can be the MTP pair of eq(0) sharing one date.
117+
openGpuMultiselect();
118+
cy.get('[role="option"]').eq(2).click();
119+
120+
cy.get('[data-testid="date-shortcut-all-time"]').should('not.be.disabled');
121+
cy.get('#gpu-comparison-date-picker').should('not.be.disabled');
122+
});
123+
124+
it('selecting a third GPU adds a third removable chip to the multiselect', () => {
125+
openGpuMultiselect();
126+
cy.get('[role="option"]').first().click();
127+
openGpuMultiselect();
128+
cy.get('[role="option"]').eq(1).click();
129+
openGpuMultiselect();
130+
cy.get('[role="option"]').eq(2).click();
131+
cy.get('[data-testid="gpu-multiselect"]')
132+
.find('[aria-label^="Remove "]')
133+
.should('have.length', 3);
134+
});
93135
});
94136
});

packages/app/src/components/dashboard-shell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export function DashboardShell({ children }: { children: React.ReactNode }) {
1111
<NudgeEngine scope="dashboard" />
1212
<UnofficialRunProvider>
1313
<main className="relative">
14-
<div className="container mx-auto px-4 lg:px-8 flex flex-col gap-4">
14+
<div className="container mx-auto px-4 lg:px-8 flex flex-col gap-2 md:gap-3">
1515
<TabNav />
1616
<GlobalFilterProvider>{children}</GlobalFilterProvider>
1717
</div>

packages/app/src/components/inference/InferenceContext.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -639,14 +639,14 @@ export function InferenceProvider({
639639
}, [dateRangeAvailableDates]);
640640

641641
// Auto-default to max date range once when GPU comparison first becomes ready.
642-
// Uses a ref to fire only on the transition from <2 GPUs to >=2, avoiding a loop
642+
// Uses a ref to fire only on the transition from 0 GPUs to >=1, avoiding a loop
643643
// when the user intentionally clears the date range.
644644
const prevGpuCountRef = useRef(selectedGPUs.length);
645645
useEffect(() => {
646-
const wasBelow = prevGpuCountRef.current < 2;
646+
const wasBelow = prevGpuCountRef.current < 1;
647647
prevGpuCountRef.current = selectedGPUs.length;
648648
if (!wasBelow) return;
649-
if (selectedGPUs.length < 2) return;
649+
if (selectedGPUs.length === 0) return;
650650
if (selectedDateRange.startDate && selectedDateRange.endDate) return;
651651
if (dateRangeAvailableDates.length < 2) return;
652652
const startDate = dateRangeAvailableDates[0];

packages/app/src/components/inference/ui/ChartDisplay.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export default function ChartDisplay() {
147147
setSelectedE2eXAxisMetric,
148148
} = useInference();
149149

150-
const comparisonReady = useMemo(() => selectedGPUs.length >= 2, [selectedGPUs]);
150+
const comparisonReady = useMemo(() => selectedGPUs.length > 0, [selectedGPUs]);
151151

152152
const [viewModes, setViewModes] = useState<Record<number, InferenceViewMode>>({});
153153
const getViewMode = (index: number): InferenceViewMode => viewModes[index] ?? 'chart';
@@ -522,14 +522,14 @@ export default function ChartDisplay() {
522522
));
523523

524524
return (
525-
<div data-testid="inference-chart-display" className="flex flex-col gap-4">
525+
<div data-testid="inference-chart-display" className="flex flex-col gap-3 md:gap-4">
526526
<section className="relative z-20">
527-
<Card>
528-
<div className="flex flex-col gap-4">
527+
<Card className="p-4 md:p-6">
528+
<div className="flex flex-col gap-3">
529529
<div className="flex items-start justify-between">
530530
<div>
531-
<h2 className="text-lg font-semibold mb-2">Inference Performance</h2>
532-
<p className="text-muted-foreground text-sm mb-4">
531+
<h2 className="text-lg font-semibold mb-1.5">Inference Performance</h2>
532+
<p className="text-muted-foreground text-sm mb-3">
533533
Inference performance metrics across different models, hardware configurations,
534534
and serving parameters.
535535
</p>

0 commit comments

Comments
 (0)