Skip to content

Commit 2f7d293

Browse files
committed
Fix chart tab crash on unknown reviewer/option references
aggregateColumn indexed refMap values with .label without checking the entry exists — any reviewer/option no longer present in the refMap (e.g. admins removed after reviewing) crashed the whole chart view. Falls back to the raw value as label. Adds a script that exercises chart aggregation against real submissions.
1 parent bfe2781 commit 2f7d293

2 files changed

Lines changed: 77 additions & 2 deletions

File tree

‎scripts/test-analytics.ts‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { getFormAnalytics } from "../src/lib/utils/forms/analytics";
2+
import { PrismaClient } from "../prisma/generated/client";
3+
import { PrismaPg } from "@prisma/adapter-pg";
4+
5+
const db = new PrismaClient({
6+
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
7+
});
8+
9+
async function main() {
10+
// Form 10 = Fall 2025 Recruitment (real reviewer activity from last season)
11+
const rows = await db.submissions.findMany({
12+
where: { form_id: BigInt(10) },
13+
include: { applications: true, users: true },
14+
});
15+
const submissions = rows.map((s: any) => ({
16+
...s,
17+
...(s.details as any),
18+
email: s.users?.email,
19+
...s.applications,
20+
}));
21+
console.log("submissions:", submissions.length);
22+
23+
// refMap exactly as the UI builds it: ONLY current admins.
24+
// Reviewers removed earlier today are absent -> ref[val] is undefined.
25+
const admins = await db.roles.findMany({
26+
where: { roles: { contains: "admin" } },
27+
include: { users: true },
28+
});
29+
const membersMap: any = {};
30+
admins.forEach((a: any) => {
31+
membersMap[a.id] = {
32+
id: a.id,
33+
label: a.users.display_name || a.users.email,
34+
};
35+
});
36+
const refMap: any = {
37+
members: membersMap,
38+
reviewer_id: "members", // person fields store the string "members"
39+
interviewer_id: "members",
40+
status: {
41+
accepted: { id: "accepted", label: "accepted" },
42+
rejected: { id: "rejected", label: "rejected" },
43+
offered: { id: "offered", label: "offered" },
44+
},
45+
faculty: {
46+
science: { id: "science", label: "Science" },
47+
},
48+
};
49+
50+
const result = getFormAnalytics(
51+
{ columns: ["status", "reviewer_id", "interviewer_id", "faculty"] },
52+
submissions,
53+
refMap,
54+
);
55+
56+
let crashed = false;
57+
for (const chart of result.analyticsData) {
58+
const unknownRefs = chart.chartData.filter(
59+
(d: any) => d.label === d.id && /^[0-9a-f-]{36}$/.test(d.id),
60+
);
61+
if (unknownRefs.length > 0) crashed = true;
62+
console.log(
63+
`chart "${chart.charInfo.title}": ${chart.chartData.length} values, ` +
64+
`unknown-ref fallbacks: ${unknownRefs.length}`,
65+
);
66+
}
67+
console.log(
68+
crashed
69+
? "PASS — unknown refs fell back to raw ids (no crash)"
70+
: "PASS — all refs resolved",
71+
);
72+
await db.$disconnect();
73+
}
74+
75+
main();

‎src/lib/utils/forms/analytics.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function aggregateColumn(
4545
acc[val] = {
4646
id: val,
4747
count: 1,
48-
label: ref ? ref[val].label : val,
48+
label: ref ? (ref[val]?.label ?? val) : val,
4949
};
5050
}
5151
});
@@ -60,7 +60,7 @@ function aggregateColumn(
6060
acc[value] = {
6161
id: value,
6262
count: 1,
63-
label: ref ? ref[value].label : value,
63+
label: ref ? (ref[value]?.label ?? value) : value,
6464
};
6565
}
6666
return acc;

0 commit comments

Comments
 (0)