Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions packages/app/cypress/e2e/quick-range-chips.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Tests for the inline QuickRangeChips that sit under the date-range picker
* in the Inference tab. Verifies the chips render only when a GPU is selected
* (and therefore a date range is needed), that clicking applies a range, and
* that the URL-restored range round-trips back to an active chip.
*/
describe('QuickRangeChips — inline below date-range trigger', () => {
beforeEach(() => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
});

it('does not render the chips when no GPU is selected', () => {
cy.visit('/inference');
cy.get('[data-testid="inference-chart-display"]').should('exist');
cy.get('[data-testid="quick-range-chips"]').should('not.exist');
});

it('renders the inline chips when a GPU is selected via URL', () => {
cy.visit('/inference?i_gpus=b200_sglang');
cy.get('[data-testid="inference-chart-display"]').should('exist');
cy.get('[data-testid="quick-range-chips"]', { timeout: 15000 }).should('be.visible');
cy.get('[data-testid="quick-range-chip-all"]').should('exist');
cy.get('[data-testid="quick-range-chip-ytd"]').should('exist');
cy.get('[data-testid="quick-range-chip-90d"]').should('exist');
cy.get('[data-testid="quick-range-chip-30d"]').should('exist');
cy.get('[data-testid="quick-range-chip-7d"]').should('exist');
});

it('lights up the matching chip when the URL range equals the "All" extent', () => {
cy.visit('/inference?i_gpus=b200_sglang');
cy.get('[data-testid="quick-range-chips"]', { timeout: 15000 }).should('be.visible');
cy.get('[data-testid="quick-range-chip-all"]').click();
cy.get('[data-testid="quick-range-chip-all"]').should('have.attr', 'data-active', 'true');
cy.get('[data-testid="quick-range-chip-all"]').should('have.attr', 'aria-pressed', 'true');
});

it('clicking a chip updates the date-range trigger label', () => {
cy.visit('/inference?i_gpus=b200_sglang');
cy.get('[data-testid="quick-range-chips"]', { timeout: 15000 }).should('be.visible');
cy.get('[data-testid="quick-range-chip-all"]').click();
// Trigger label should now show the start - end date pair, not the placeholder
cy.contains('button', 'Select date range').should('not.exist');
});
});
9 changes: 9 additions & 0 deletions packages/app/src/components/inference/ui/ChartControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { DateRangePicker } from '@/components/ui/date-range-picker';
import { LabelWithTooltip } from '@/components/ui/label-with-tooltip';
import { MultiSelect } from '@/components/ui/multi-select';
import { QuickRangeChips } from '@/components/ui/quick-range-chips';
import {
Select,
SelectContent,
Expand Down Expand Up @@ -343,6 +344,14 @@ export default function ChartControls({ hideGpuComparison = false }: ChartContro
: ''
}
/>
{dateRangeAvailableDates && dateRangeAvailableDates.length >= 2 && (
<QuickRangeChips
availableDates={dateRangeAvailableDates}
currentRange={selectedDateRange}
onApply={handleDateRangeChange}
source="inline"
/>
)}
</div>
)}
</div>
Expand Down
68 changes: 15 additions & 53 deletions packages/app/src/components/ui/date-range-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { QuickRangeChips } from '@/components/ui/quick-range-chips';
import { cn } from '@/lib/utils';

export interface DateRange {
Expand Down Expand Up @@ -180,6 +181,19 @@ export function DateRangePicker({
)}
</DialogDescription>
</DialogHeader>
{availableDates && availableDates.length >= 2 && (
<div className="flex items-center gap-3 pt-2">
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Quick range:
</span>
<QuickRangeChips
availableDates={availableDates}
currentRange={tempRange}
onApply={(range) => setTempRange(range)}
source="dialog"
/>
</div>
)}
<div className="py-4 relative">
<CalendarGrid
dateRange={tempRange}
Expand Down Expand Up @@ -240,59 +254,7 @@ export function DateRangePicker({
)}
</div>
{error && <p className="text-md text-center text-red-500">{error}</p>}
<DialogFooter className="flex-row justify-between sm:justify-between">
{availableDates && availableDates.length >= 2 ? (
<div className="flex flex-wrap gap-1.5">
{[
{
label: 'Max Range',
getRange: () => ({
startDate: availableDates[0],
endDate: availableDates.at(-1)!,
}),
},
{
label: 'Last 90 Days',
getRange: () => {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 90);
const cutoffStr = cutoff.toISOString().slice(0, 10);
const filtered = availableDates.filter((d) => d >= cutoffStr);
if (filtered.length < 2) return null;
return { startDate: filtered[0], endDate: filtered.at(-1)! };
},
},
{
label: 'Last 30 Days',
getRange: () => {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 30);
const cutoffStr = cutoff.toISOString().slice(0, 10);
const filtered = availableDates.filter((d) => d >= cutoffStr);
if (filtered.length < 2) return null;
return { startDate: filtered[0], endDate: filtered.at(-1)! };
},
},
].map(({ label, getRange }) => {
const range = getRange();
if (!range) return null;
return (
<Button
key={label}
variant="outline"
onClick={() => {
setTempRange(range);
track('date_range_picker_quick_select', { label });
}}
>
{label}
</Button>
);
})}
</div>
) : (
<div />
)}
<DialogFooter>
<div className="flex gap-2">
<DialogClose asChild>
<Button variant="outline" onClick={handleCancel}>
Expand Down
112 changes: 112 additions & 0 deletions packages/app/src/components/ui/quick-range-chips.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use client';

import { track } from '@/lib/analytics';
import { Button } from '@/components/ui/button';
import { TooltipContent, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip';
import {
buildQuickRangePresets,
matchActivePreset,
type QuickRangePreset,
} from '@/lib/quick-range-presets';
import { cn } from '@/lib/utils';

export type QuickRangeChipsSource = 'inline' | 'dialog';

interface QuickRangeChipsProps {
availableDates: string[];
currentRange: { startDate: string; endDate: string };
onApply: (range: { startDate: string; endDate: string }) => void;
/**
* Where the chips are rendered, used in analytics events so we can tell whether
* the inline (low-friction) or in-dialog placement is the one users actually click.
*/
source: QuickRangeChipsSource;
/** Optional inject point for testing — defaults to `new Date()` at call time. */
today?: Date;
className?: string;
}

export function QuickRangeChips({
availableDates,
currentRange,
onApply,
source,
today,
className,
}: QuickRangeChipsProps) {
// Hide the affordance entirely when there's not enough data — a single chip with
// five disabled options would be more confusing than absence.
if (availableDates.length < 2) return null;

const presets = buildQuickRangePresets(today);
const activeId = matchActivePreset(currentRange, availableDates, today);

return (
<div
className={cn('flex flex-wrap gap-1.5', className)}
data-testid="quick-range-chips"
role="group"
aria-label="Quick date range"
>
{presets.map((preset) => (
<Chip
key={preset.id}
preset={preset}
availableDates={availableDates}
isActive={activeId === preset.id}
onSelect={(range) => {
track('inference_quick_range_selected', {
id: preset.id,
source,
startDate: range.startDate,
endDate: range.endDate,
});
onApply(range);
}}
/>
))}
</div>
);
}

interface ChipProps {
preset: QuickRangePreset;
availableDates: string[];
isActive: boolean;
onSelect: (range: { startDate: string; endDate: string }) => void;
}

function Chip({ preset, availableDates, isActive, onSelect }: ChipProps) {
const range = preset.getRange(availableDates);
const disabled = range === null;

const button = (
<Button
type="button"
size="sm"
variant={isActive ? 'default' : 'outline'}
disabled={disabled}
onClick={() => range && onSelect(range)}
data-testid={`quick-range-chip-${preset.id}`}
data-active={isActive ? 'true' : 'false'}
aria-pressed={isActive}
className="h-7 px-2.5 text-xs"
>
{preset.label}
</Button>
);

if (!disabled) return button;

// Tell the user *why* it's disabled — discoverability beats cleanliness for newcomers
// who haven't yet understood the data model.
return (
<TooltipRoot>
<TooltipTrigger asChild>
{/* span wrapper so the tooltip still triggers on the disabled button */}
<span className="inline-flex">{button}</span>
</TooltipTrigger>
<TooltipContent>Not enough data points in this window</TooltipContent>
</TooltipRoot>
);
}
102 changes: 102 additions & 0 deletions packages/app/src/lib/quick-range-presets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { describe, expect, it } from 'vitest';

import { buildQuickRangePresets, matchActivePreset } from './quick-range-presets';

const TODAY = new Date('2026-03-25T12:00:00Z');

const DATES = [
'2025-09-15',
'2025-10-01',
'2025-12-20',
'2025-12-30',
'2026-01-10',
'2026-02-05',
'2026-03-01',
'2026-03-20',
'2026-03-25',
];

describe('buildQuickRangePresets', () => {
it('returns five presets in fixed order', () => {
const presets = buildQuickRangePresets(TODAY);
expect(presets.map((p) => p.id)).toEqual(['7d', '30d', '90d', 'ytd', 'all']);
});

it('"All" spans first to last available date', () => {
const all = buildQuickRangePresets(TODAY).find((p) => p.id === 'all')!;
expect(all.getRange(DATES)).toEqual({ startDate: '2025-09-15', endDate: '2026-03-25' });
});

it('"YTD" starts from Jan 1 of today\'s year', () => {
const ytd = buildQuickRangePresets(TODAY).find((p) => p.id === 'ytd')!;
expect(ytd.getRange(DATES)).toEqual({ startDate: '2026-01-10', endDate: '2026-03-25' });
});

it('"90D" filters to the last 90 days from today', () => {
const ninety = buildQuickRangePresets(TODAY).find((p) => p.id === '90d')!;
// 2026-03-25 minus 90 days = 2025-12-25, so 2025-12-30 is the first in-window date
expect(ninety.getRange(DATES)).toEqual({ startDate: '2025-12-30', endDate: '2026-03-25' });
});

it('"30D" returns null when only one in-window date exists', () => {
const thirty = buildQuickRangePresets(TODAY).find((p) => p.id === '30d')!;
// 2026-03-25 minus 30 days = 2026-02-23; only 2026-03-01, 03-20, 03-25 qualify → 3 ≥ 2
const result = thirty.getRange(DATES);
expect(result).toEqual({ startDate: '2026-03-01', endDate: '2026-03-25' });
});

it('"30D" returns null when zero in-window dates exist', () => {
const thirty = buildQuickRangePresets(TODAY).find((p) => p.id === '30d')!;
expect(thirty.getRange(['2024-01-01', '2024-01-02'])).toBeNull();
});

it('"7D" returns null when fewer than 2 dates in window', () => {
const seven = buildQuickRangePresets(TODAY).find((p) => p.id === '7d')!;
// 2026-03-25 minus 7 = 2026-03-18; 03-20 and 03-25 qualify → 2 ≥ 2
expect(seven.getRange(DATES)).toEqual({ startDate: '2026-03-20', endDate: '2026-03-25' });
});

it('"7D" returns null with only one dated point in window', () => {
const seven = buildQuickRangePresets(TODAY).find((p) => p.id === '7d')!;
expect(seven.getRange(['2026-03-25', '2025-01-01'].toSorted())).toBeNull();
});

it('"All" returns null when fewer than 2 total dates', () => {
const all = buildQuickRangePresets(TODAY).find((p) => p.id === 'all')!;
expect(all.getRange(['2026-03-25'])).toBeNull();
expect(all.getRange([])).toBeNull();
});
});

describe('matchActivePreset', () => {
it('matches the "All" preset when range covers full extent', () => {
expect(
matchActivePreset({ startDate: '2025-09-15', endDate: '2026-03-25' }, DATES, TODAY),
).toBe('all');
});

it('matches "YTD" when range spans Jan 1 of current year to last date', () => {
expect(
matchActivePreset({ startDate: '2026-01-10', endDate: '2026-03-25' }, DATES, TODAY),
).toBe('ytd');
});

it('returns null for a custom range that no preset produces', () => {
expect(
matchActivePreset({ startDate: '2025-10-01', endDate: '2026-02-05' }, DATES, TODAY),
).toBeNull();
});

it('returns null for an empty range', () => {
expect(matchActivePreset({ startDate: '', endDate: '' }, DATES, TODAY)).toBeNull();
});

it('returns the first preset id whose range matches when multiple presets coincide', () => {
// With these dates, 90d, ytd, and "all" all collapse to the same window; we expect the
// earliest (90d) to win because we iterate in fixed preset order.
const collapsed = ['2026-01-10', '2026-03-25'];
expect(
matchActivePreset({ startDate: '2026-01-10', endDate: '2026-03-25' }, collapsed, TODAY),
).toBe('90d');
});
});
Loading
Loading