Skip to content

Commit 5a6fb9c

Browse files
authored
Merge pull request #79 from fparrav/fix/dashboard-timeline-chart
fix: Memory Query Load chart timeline ordering and period selection
2 parents dc95dde + 9ca7331 commit 5a6fb9c

File tree

2 files changed

+67
-22
lines changed

2 files changed

+67
-22
lines changed

backend/src/server/routes/dashboard.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,15 +277,50 @@ export function dash(app: any) {
277277
const mem_table = get_mem_table();
278278
const hrs = parseInt(req.query.hours || "24");
279279
const strt = Date.now() - hrs * 60 * 60 * 1000;
280+
281+
// Use different grouping based on time range
282+
let displayFormat: string;
283+
let sortFormat: string;
284+
let timeKey: string;
285+
if (hrs <= 24) {
286+
// For 24 hours or less, group by date+hour for sorting, display only hour
287+
displayFormat = is_pg
288+
? "to_char(to_timestamp(created_at/1000), 'HH24:00')"
289+
: "strftime('%H:00', datetime(created_at/1000, 'unixepoch', 'localtime'))";
290+
sortFormat = is_pg
291+
? "to_char(to_timestamp(created_at/1000), 'YYYY-MM-DD HH24:00')"
292+
: "strftime('%Y-%m-%d %H:00', datetime(created_at/1000, 'unixepoch', 'localtime'))";
293+
timeKey = "hour";
294+
} else if (hrs <= 168) {
295+
// For up to 7 days, group by day
296+
displayFormat = is_pg
297+
? "to_char(to_timestamp(created_at/1000), 'MM-DD')"
298+
: "strftime('%m-%d', datetime(created_at/1000, 'unixepoch', 'localtime'))";
299+
sortFormat = is_pg
300+
? "to_char(to_timestamp(created_at/1000), 'YYYY-MM-DD')"
301+
: "strftime('%Y-%m-%d', datetime(created_at/1000, 'unixepoch', 'localtime'))";
302+
timeKey = "day";
303+
} else {
304+
// For longer periods, group by week (ISO week format for consistency)
305+
displayFormat = is_pg
306+
? "to_char(to_timestamp(created_at/1000), 'IYYY-\"W\"IW')"
307+
: "strftime('%Y-W%W', datetime(created_at/1000, 'unixepoch', 'localtime'))";
308+
sortFormat = displayFormat;
309+
timeKey = "week";
310+
}
311+
280312
const tl = await all_async(
281313
is_pg
282-
? `SELECT primary_sector, to_char(to_timestamp(created_at/1000), 'HH24:00') as hour, COUNT(*) as count
283-
FROM ${mem_table} WHERE created_at > $1 GROUP BY primary_sector, hour ORDER BY hour`
284-
: `SELECT primary_sector, strftime('%H:00', datetime(created_at/1000, 'unixepoch')) as hour, COUNT(*) as count
285-
FROM ${mem_table} WHERE created_at > ? GROUP BY primary_sector, hour ORDER BY hour`,
314+
? `SELECT primary_sector, ${displayFormat} as label, ${sortFormat} as sort_key, COUNT(*) as count
315+
FROM ${mem_table} WHERE created_at > $1 GROUP BY primary_sector, ${sortFormat} ORDER BY sort_key`
316+
: `SELECT primary_sector, ${displayFormat} as label, ${sortFormat} as sort_key, COUNT(*) as count
317+
FROM ${mem_table} WHERE created_at > ? GROUP BY primary_sector, ${sortFormat} ORDER BY sort_key`,
286318
[strt],
287319
);
288-
res.json({ timeline: tl });
320+
res.json({
321+
timeline: tl.map((row: any) => ({ ...row, hour: row.label })),
322+
grouping: timeKey,
323+
});
289324
} catch (e: any) {
290325
res.status(500).json({ err: "internal", message: e.message });
291326
}

dashboard/app/page.tsx

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function Dashboard() {
3131
const [maintenanceStats, setMaintenanceStats] = useState<any>({})
3232
const [systemHealth, setSystemHealth] = useState<any>({})
3333
const [backendHealth, setBackendHealth] = useState<any>({})
34+
const [queryLoadPeriod, setQueryLoadPeriod] = useState("24")
3435

3536
useEffect(() => {
3637
fetchDashboardData()
@@ -41,7 +42,7 @@ export default function Dashboard() {
4142
clearInterval(dataInterval)
4243
clearInterval(healthInterval)
4344
}
44-
}, [])
45+
}, [queryLoadPeriod])
4546

4647
const fetchDashboardData = async () => {
4748
try {
@@ -100,28 +101,33 @@ export default function Dashboard() {
100101
}
101102

102103
// Fetch sector timeline
103-
const timelineRes = await fetch(`${API_BASE_URL}/dashboard/sectors/timeline?hours=24`, {
104+
const timelineRes = await fetch(`${API_BASE_URL}/dashboard/sectors/timeline?hours=${queryLoadPeriod}`, {
104105
headers: getHeaders()
105106
})
106107
if (timelineRes.ok) {
107108
const timeline = await timelineRes.json()
108109
const grouped: Record<string, any> = {}
109110

110111
timeline.timeline?.forEach((item: any) => {
111-
if (!grouped[item.hour]) {
112-
grouped[item.hour] = { hour: item.hour }
112+
// Use sort_key for grouping to distinguish same hours on different days
113+
const key = item.sort_key || item.hour
114+
if (!grouped[key]) {
115+
grouped[key] = { hour: item.hour, sort_key: key }
113116
}
114-
grouped[item.hour][item.primary_sector] = item.count
117+
grouped[key][item.primary_sector] = item.count
115118
})
116119

117-
const chartData = Object.values(grouped).map((item: any) => ({
118-
hour: item.hour,
119-
semantic: item.semantic || 0,
120-
episodic: item.episodic || 0,
121-
procedural: item.procedural || 0,
122-
emotional: item.emotional || 0,
123-
reflective: item.reflective || 0,
124-
}))
120+
// Explicitly sort by sort_key for proper chronological ordering
121+
const chartData = Object.values(grouped)
122+
.sort((a: any, b: any) => (a.sort_key || a.hour).localeCompare(b.sort_key || b.hour))
123+
.map((item: any) => ({
124+
hour: item.hour,
125+
semantic: item.semantic || 0,
126+
episodic: item.episodic || 0,
127+
procedural: item.procedural || 0,
128+
emotional: item.emotional || 0,
129+
reflective: item.reflective || 0,
130+
}))
125131

126132
setQpsData(chartData)
127133
}
@@ -272,10 +278,14 @@ export default function Dashboard() {
272278
<div className="flex items-center justify-between mb-4">
273279
<h2 className="text-xl font-semibold text-[#f4f4f5]">Memory Query Load</h2>
274280
<div className="flex gap-2">
275-
<select className="rounded-xl p-2 pl-4 border border-stone-800 bg-stone-950 hover:bg-stone-900/50 hover:text-stone-300 text-sm font-medium text-stone-400 outline-none cursor-pointer transition-colors">
276-
<option className="bg-stone-950">24 hours</option>
277-
<option className="bg-stone-950">7 days</option>
278-
<option className="bg-stone-950">30 days</option>
281+
<select
282+
value={queryLoadPeriod}
283+
onChange={(e) => setQueryLoadPeriod(e.target.value)}
284+
className="rounded-xl p-2 pl-4 border border-stone-800 bg-stone-950 hover:bg-stone-900/50 hover:text-stone-300 text-sm font-medium text-stone-400 outline-none cursor-pointer transition-colors"
285+
>
286+
<option value="24" className="bg-stone-950">24 hours</option>
287+
<option value="168" className="bg-stone-950">7 days</option>
288+
<option value="720" className="bg-stone-950">30 days</option>
279289
</select>
280290
</div>
281291
</div>

0 commit comments

Comments
 (0)