Merge pull request #1500 from BradGroux/fix/export-utc-dates-1499

fix: use UTC calendar bounds for metrics export
This commit is contained in:
Brad Groux 2026-09-04 11:51:29 -05:00 • committed by GitHub
commit a71493dff4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 1 deletions

View file

@ -72,6 +72,8 @@ Independent specification and standards reviews found no actionable source issue
### Metrics export
Export dates use inclusive UTC calendar-day bounds: From starts at 00:00:00.000Z and To ends at 23:59:59.999Z on the selected dates. The dialog states this convention. Exact-query component checks cover ordinary dates and daylight-saving transition dates; run the date slice under UTC, America/Chicago, and Asia/Tokyo to verify independence from the host timezone.
`task-export-popout.spec.ts` opens export from task History/Metrics and tests both themes and motion settings at the three viewport/text combinations. It checks fixed footer geometry and hit targets, viewport containment, nested inert state, guarded Escape/header/backdrop dismissal, disabled filters while exporting, retained filters after failure, and a successful synthetic download on retry with an identical query and exact opener restoration. The retry uses a response with no filename header and verifies the generic fallback name. No real telemetry export is performed.
The original browser case reproduced dismissal while the request was pending. Export now uses a synchronous submission/dismissal lock, exposes an inline error, and preserves scope/date filters on failure. The error receives focus without native scrolling and is then centered in the primary scroller; focus alone left its bottom edge clipped at minimum size. Browser and component checks verify the focus/scroll behavior. The component regression also tests immediate duplicate submission and restored Cancel availability.

View file

@ -246,6 +246,27 @@ describe('dashboard Mantine drilldown surfaces', () => {
expect(onOpenChange).toHaveBeenCalledWith(false);
});
it.each(['2026-09-02', '2026-03-08', '2026-11-01'])(
'exports the full UTC calendar day for %s regardless of host timezone',
async (date) => {
const request = vi
.spyOn(apiHelpers, 'apiResponse')
.mockRejectedValueOnce(new Error('Fixture export failed'));
renderWithProviders(<ExportDialog open onOpenChange={vi.fn()} taskId="task-export" />);
const dialog = screen.getByRole('dialog', { name: 'Export Metrics' });
fireEvent.change(within(dialog).getByLabelText('From'), { target: { value: date } });
fireEvent.change(within(dialog).getByLabelText('To'), { target: { value: date } });
fireEvent.click(within(dialog).getByRole('button', { name: 'Export' }));
await waitFor(() => expect(request).toHaveBeenCalledTimes(1));
const url = new URL(String(request.mock.calls[0][0]), 'http://fixture.local');
expect(url.searchParams.get('from')).toBe(`${date}T00:00:00.000Z`);
expect(url.searchParams.get('to')).toBe(`${date}T23:59:59.999Z`);
await waitFor(() =>
expect(within(dialog).getByRole('alert').textContent).toContain('Fixture export failed')
);
}
);
it('renders dashboard drilldown content through direct Mantine primitives and preserves selection', async () => {
const user = userEvent.setup();
const onTaskClick = vi.fn();

View file

@ -89,7 +89,7 @@ export function ExportDialog({
}
if (toDate) {
const toDateTime = new Date(toDate);
toDateTime.setHours(23, 59, 59, 999);
toDateTime.setUTCHours(23, 59, 59, 999);
params.set('to', toDateTime.toISOString());
}
@ -200,6 +200,9 @@ export function ExportDialog({
/>
)}
<Text size="xs" c="dimmed">
Dates use UTC. Both selected days are included.
</Text>
<TextInput
label="From"
disabled={isExporting}