fix: hide the next heartbeat and consent date in admin usage stats while heartbeats are off

This commit is contained in:
Linus Rath
2026-10-28 10:24:34 +01:00
parent 161c9d1de3
commit e3fd576543
2 changed files with 45 additions and 6 deletions
@@ -81,4 +81,30 @@ describe('admin telemetry page', () => {
expect.objectContaining({ url: '/api/admin/telemetry', body: { action: 'set-consent', consent: 'on' } }),
]));
});
it('shows no next send and no consent date while heartbeats are off', async () => {
routes['/api/admin/telemetry'] = {
...(routes['/api/admin/telemetry'] as object),
consent: 'off', consentedAt: '2026-10-01T10:00:00Z', lastSentAt: '2026-10-02T10:00:00Z',
nextScheduledAt: new Date(Date.now() - 7 * 3600_000).toISOString(),
};
render(<TelemetryPage />);
await screen.findByRole('switch', { name: 'Send anonymous heartbeats' });
expect(screen.queryByText('Next scheduled')).toBeNull();
expect(screen.queryByText('Consented at')).toBeNull();
expect(screen.queryByText(/hours ago/)).toBeNull();
expect(screen.getByText('Last sent')).toBeTruthy();
});
it('shows a past next send as due, not as "hours ago", while heartbeats are on', async () => {
routes['/api/admin/telemetry'] = {
...(routes['/api/admin/telemetry'] as object),
consent: 'on', consentedAt: '2026-10-01T10:00:00Z',
nextScheduledAt: new Date(Date.now() - 7 * 3600_000).toISOString(),
};
render(<TelemetryPage />);
await screen.findByText('Next scheduled');
expect(screen.getByText('Due now')).toBeTruthy();
expect(screen.getByText('Consented at')).toBeTruthy();
});
});
+19 -6
View File
@@ -40,6 +40,13 @@ function timeAgo(iso: string | null): string {
return `${days} days ago`;
}
/** When the next heartbeat goes out; a time already past means it is due. */
function nextRun(iso: string | null): string {
if (!iso) return '-';
const at = new Date(iso);
return at.getTime() <= Date.now() ? 'Due now' : at.toLocaleString();
}
export function TelemetryPage() {
const [status, setStatus] = useState<TelemetryStatus | null>(null);
const [loading, setLoading] = useState(true);
@@ -177,12 +184,18 @@ export function TelemetryPage() {
<SettingItem label="Last sent">
<span className="text-sm text-foreground">{timeAgo(status.lastSentAt)}</span>
</SettingItem>
<SettingItem label="Next scheduled">
<span className="text-sm text-foreground">{timeAgo(status.nextScheduledAt)}</span>
</SettingItem>
<SettingItem label="Consented at">
<span className="text-sm text-foreground">{status.consentedAt ? new Date(status.consentedAt).toLocaleString() : '-'}</span>
</SettingItem>
{/* Only a running schedule has a next send and a consent date; with
heartbeats off they were stale ("Next scheduled: 7 hours ago"). */}
{isOn && (
<>
<SettingItem label="Next scheduled">
<span className="text-sm text-foreground">{nextRun(status.nextScheduledAt)}</span>
</SettingItem>
<SettingItem label="Consented at">
<span className="text-sm text-foreground">{status.consentedAt ? new Date(status.consentedAt).toLocaleString() : '-'}</span>
</SettingItem>
</>
)}
</SettingsSection>
<SettingsSection