Skip to content

Commit 8ef4d81

Browse files
authored
fix(consensus): apply selected time range to daily overview charts (#443)
The consensus overview daily queries (30d, 90d, 180d, 1y, 2y) fetched the full history with day_start_date_like='20%' and page_size=10000 and never trimmed the result, so every daily range rendered identically to "all". The daily API endpoints expose no range filter on their primary key, so the window is now applied client-side after fetch via a cutoff date derived from the range's day count. The "all" window (null days) is unaffected. Claude-Session: https://claude.ai/code/session_01Mp4aKNXPmN6NfMuNGbf3Qm
1 parent e9a1c77 commit 8ef4d81

3 files changed

Lines changed: 114 additions & 16 deletions

File tree

src/pages/ethereum/consensus/overview/IndexPage.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ import {
5959
formatTooltipDate,
6060
buildTooltipHtml,
6161
formatBand,
62+
dailyWindowStartDate,
63+
trimDailyRecords,
6264
buildBlobCountChartConfig,
6365
buildAttestationParticipationChartConfig,
6466
buildHeadVoteCorrectnessChartConfig,
@@ -92,6 +94,10 @@ export function IndexPage(): JSX.Element {
9294
return now - config.days * 24 * 60 * 60;
9395
}, [config.days]);
9496

97+
// Daily endpoints have no server-side range filter on their primary key, so the
98+
// selected window is applied to daily records client-side via this cutoff date.
99+
const dailyStartDate = useMemo(() => dailyWindowStartDate(config.days), [config.days]);
100+
95101
// --- Queries ---
96102

97103
const blobHourlyQuery = useQuery({
@@ -235,63 +241,80 @@ export function IndexPage(): JSX.Element {
235241
const blobRecords = useMemo(
236242
() =>
237243
isDaily
238-
? [...(blobDailyQuery.data?.fct_blob_count_daily ?? [])].reverse()
244+
? trimDailyRecords([...(blobDailyQuery.data?.fct_blob_count_daily ?? [])].reverse(), dailyStartDate)
239245
: blobHourlyQuery.data?.fct_blob_count_hourly,
240-
[isDaily, blobDailyQuery.data, blobHourlyQuery.data]
246+
[isDaily, blobDailyQuery.data, blobHourlyQuery.data, dailyStartDate]
241247
);
242248

243249
const attnRecords = useMemo(
244250
() =>
245251
isDaily
246-
? [...(attnDailyQuery.data?.fct_attestation_participation_rate_daily ?? [])].reverse()
252+
? trimDailyRecords(
253+
[...(attnDailyQuery.data?.fct_attestation_participation_rate_daily ?? [])].reverse(),
254+
dailyStartDate
255+
)
247256
: attnHourlyQuery.data?.fct_attestation_participation_rate_hourly,
248-
[isDaily, attnDailyQuery.data, attnHourlyQuery.data]
257+
[isDaily, attnDailyQuery.data, attnHourlyQuery.data, dailyStartDate]
249258
);
250259

251260
const hvRecords = useMemo(
252261
() =>
253262
isDaily
254-
? [...(hvDailyQuery.data?.fct_head_vote_correctness_rate_daily ?? [])].reverse()
263+
? trimDailyRecords(
264+
[...(hvDailyQuery.data?.fct_head_vote_correctness_rate_daily ?? [])].reverse(),
265+
dailyStartDate
266+
)
255267
: hvHourlyQuery.data?.fct_head_vote_correctness_rate_hourly,
256-
[isDaily, hvDailyQuery.data, hvHourlyQuery.data]
268+
[isDaily, hvDailyQuery.data, hvHourlyQuery.data, dailyStartDate]
257269
);
258270

259271
const reorgRecords = useMemo(
260272
() =>
261-
isDaily ? [...(reorgDailyQuery.data?.fct_reorg_daily ?? [])].reverse() : reorgHourlyQuery.data?.fct_reorg_hourly,
262-
[isDaily, reorgDailyQuery.data, reorgHourlyQuery.data]
273+
isDaily
274+
? trimDailyRecords([...(reorgDailyQuery.data?.fct_reorg_daily ?? [])].reverse(), dailyStartDate)
275+
: reorgHourlyQuery.data?.fct_reorg_hourly,
276+
[isDaily, reorgDailyQuery.data, reorgHourlyQuery.data, dailyStartDate]
263277
);
264278

265279
const missedSlotRecords = useMemo(
266280
() =>
267281
isDaily
268-
? [...(missedSlotDailyQuery.data?.fct_missed_slot_rate_daily ?? [])].reverse()
282+
? trimDailyRecords([...(missedSlotDailyQuery.data?.fct_missed_slot_rate_daily ?? [])].reverse(), dailyStartDate)
269283
: missedSlotHourlyQuery.data?.fct_missed_slot_rate_hourly,
270-
[isDaily, missedSlotDailyQuery.data, missedSlotHourlyQuery.data]
284+
[isDaily, missedSlotDailyQuery.data, missedSlotHourlyQuery.data, dailyStartDate]
271285
);
272286

273287
const proposalStatusRecords = useMemo(
274288
() =>
275289
isDaily
276-
? [...(proposalStatusDailyQuery.data?.fct_block_proposal_status_daily ?? [])].reverse()
290+
? trimDailyRecords(
291+
[...(proposalStatusDailyQuery.data?.fct_block_proposal_status_daily ?? [])].reverse(),
292+
dailyStartDate
293+
)
277294
: proposalStatusHourlyQuery.data?.fct_block_proposal_status_hourly,
278-
[isDaily, proposalStatusDailyQuery.data, proposalStatusHourlyQuery.data]
295+
[isDaily, proposalStatusDailyQuery.data, proposalStatusHourlyQuery.data, dailyStartDate]
279296
);
280297

281298
const inclusionDelayRecords = useMemo(
282299
() =>
283300
isDaily
284-
? [...(inclusionDelayDailyQuery.data?.fct_attestation_inclusion_delay_daily ?? [])].reverse()
301+
? trimDailyRecords(
302+
[...(inclusionDelayDailyQuery.data?.fct_attestation_inclusion_delay_daily ?? [])].reverse(),
303+
dailyStartDate
304+
)
285305
: inclusionDelayHourlyQuery.data?.fct_attestation_inclusion_delay_hourly,
286-
[isDaily, inclusionDelayDailyQuery.data, inclusionDelayHourlyQuery.data]
306+
[isDaily, inclusionDelayDailyQuery.data, inclusionDelayHourlyQuery.data, dailyStartDate]
287307
);
288308

289309
const proposerRewardRecords = useMemo(
290310
() =>
291311
isDaily
292-
? [...(proposerRewardDailyQuery.data?.fct_proposer_reward_daily ?? [])].reverse()
312+
? trimDailyRecords(
313+
[...(proposerRewardDailyQuery.data?.fct_proposer_reward_daily ?? [])].reverse(),
314+
dailyStartDate
315+
)
293316
: proposerRewardHourlyQuery.data?.fct_proposer_reward_hourly,
294-
[isDaily, proposerRewardDailyQuery.data, proposerRewardHourlyQuery.data]
317+
[isDaily, proposerRewardDailyQuery.data, proposerRewardHourlyQuery.data, dailyStartDate]
295318
);
296319

297320
// --- Unified time keys from all datasets ---
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { dailyWindowStartDate, trimDailyRecords } from './overview.utils';
3+
4+
describe('dailyWindowStartDate', () => {
5+
// 2026-07-13T12:00:00Z
6+
const now = Date.UTC(2026, 6, 13, 12, 0, 0);
7+
8+
it('returns undefined for an unbounded ("all") window', () => {
9+
expect(dailyWindowStartDate(null, now)).toBeUndefined();
10+
});
11+
12+
it('spans exactly 30 calendar days including today', () => {
13+
expect(dailyWindowStartDate(30, now)).toBe('2026-06-14');
14+
});
15+
16+
it('spans exactly 180 calendar days including today', () => {
17+
expect(dailyWindowStartDate(180, now)).toBe('2026-01-15');
18+
});
19+
20+
it('spans exactly 365 calendar days including today', () => {
21+
expect(dailyWindowStartDate(365, now)).toBe('2025-07-14');
22+
});
23+
});
24+
25+
describe('trimDailyRecords', () => {
26+
const records = [
27+
{ day_start_date: '2025-01-01', value: 1 },
28+
{ day_start_date: '2026-01-14', value: 2 },
29+
{ day_start_date: '2026-07-13', value: 3 },
30+
];
31+
32+
it('returns an empty array for nullish records', () => {
33+
expect(trimDailyRecords(undefined, '2026-01-01')).toEqual([]);
34+
});
35+
36+
it('returns records unchanged when startDate is undefined (the "all" window)', () => {
37+
expect(trimDailyRecords(records, undefined)).toBe(records);
38+
});
39+
40+
it('keeps only records on or after the cutoff (inclusive)', () => {
41+
expect(trimDailyRecords(records, '2026-01-14')).toEqual([
42+
{ day_start_date: '2026-01-14', value: 2 },
43+
{ day_start_date: '2026-07-13', value: 3 },
44+
]);
45+
});
46+
47+
it('treats a missing day_start_date as excluded', () => {
48+
const withMissing = [{ value: 9 }, ...records];
49+
expect(trimDailyRecords(withMissing, '2025-01-01')).toEqual(records);
50+
});
51+
});

src/pages/ethereum/consensus/overview/overview.utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ export function formatBand(lower: number | undefined, upper: number | undefined)
3939
return `${l.toFixed(2)}${u.toFixed(2)}`;
4040
}
4141

42+
/**
43+
* Computes the inclusive start date (YYYY-MM-DD, UTC) for a daily window of `days` calendar days.
44+
* The window spans exactly `days` buckets ending on the current UTC day (today and the `days - 1`
45+
* preceding days). Returns undefined for an unbounded ("all") window.
46+
*/
47+
export function dailyWindowStartDate(days: number | null, now: number = Date.now()): string | undefined {
48+
if (days === null) return undefined;
49+
return new Date(now - (days - 1) * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
50+
}
51+
52+
/**
53+
* Trims daily records to those on or after `startDate` (inclusive, string comparison on YYYY-MM-DD).
54+
* The daily API endpoints have no range filter on their primary key, so the window is applied client-side.
55+
* A nullish `startDate` (the "all" window) returns the records unchanged.
56+
*/
57+
export function trimDailyRecords<T extends { day_start_date?: string }>(
58+
records: T[] | undefined,
59+
startDate: string | undefined
60+
): T[] {
61+
if (!records) return [];
62+
if (!startDate) return records;
63+
return records.filter(r => (r.day_start_date ?? '') >= startDate);
64+
}
65+
4266
/** @deprecated Use formatBand instead */
4367
export const formatBlobBand = formatBand;
4468

0 commit comments

Comments
 (0)