fix: Admin Insights page could crash on a single query failure (v0.55.2)
Two changes: 1. Replaced positional `GROUP BY 1` (not used anywhere else in this codebase) with the conventional pattern of repeating the actual grouped expression — matches every other groupBy call site in the app. 2. Switched Promise.all -> Promise.allSettled across the six independent aggregate queries feeding the six charts, defaulting a failed one to an empty array (that chart just renders "No data yet") instead of taking the whole page down. Logs the failure server-side either way. Couldn't reproduce locally (no DB in this sandbox), but confirmed the allSettled path works: the production build itself hit a real connection failure against the (absent) local DB and degraded cleanly instead of crashing, which is exactly the resilience this fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,28 +43,24 @@ export default async function AdminInsightsPage() {
|
||||
const since = new Date();
|
||||
since.setDate(since.getDate() - DAYS);
|
||||
|
||||
const [
|
||||
signupRows,
|
||||
recipeRows,
|
||||
tierRows,
|
||||
visibilityRows,
|
||||
usageRows,
|
||||
ticketRows,
|
||||
] = await Promise.all([
|
||||
// Promise.allSettled, not all — six independent aggregate queries feeding
|
||||
// six independent charts; one query breaking (e.g. a table that's empty
|
||||
// in a fresh install) shouldn't take down every chart on the page.
|
||||
const results = await Promise.allSettled([
|
||||
db
|
||||
.select({ day: sql<string>`to_char(${users.createdAt}, 'YYYY-MM-DD')`, n: sql<number>`count(*)::int` })
|
||||
.select({ day: sql<string>`to_char(${users.createdAt}, 'YYYY-MM-DD')`.as("day"), n: sql<number>`count(*)::int` })
|
||||
.from(users)
|
||||
.where(gte(users.createdAt, since))
|
||||
.groupBy(sql`1`),
|
||||
.groupBy(sql`to_char(${users.createdAt}, 'YYYY-MM-DD')`),
|
||||
db
|
||||
.select({
|
||||
day: sql<string>`to_char(${recipes.createdAt}, 'YYYY-MM-DD')`,
|
||||
day: sql<string>`to_char(${recipes.createdAt}, 'YYYY-MM-DD')`.as("day"),
|
||||
aiGenerated: recipes.aiGenerated,
|
||||
n: sql<number>`count(*)::int`,
|
||||
})
|
||||
.from(recipes)
|
||||
.where(gte(recipes.createdAt, since))
|
||||
.groupBy(sql`1`, recipes.aiGenerated),
|
||||
.groupBy(sql`to_char(${recipes.createdAt}, 'YYYY-MM-DD')`, recipes.aiGenerated),
|
||||
db.select({ tier: users.tier, n: sql<number>`count(*)::int` }).from(users).groupBy(users.tier),
|
||||
db.select({ visibility: recipes.visibility, n: sql<number>`count(*)::int` }).from(recipes).groupBy(recipes.visibility),
|
||||
db
|
||||
@@ -74,6 +70,21 @@ export default async function AdminInsightsPage() {
|
||||
db.select({ status: supportTickets.status, n: sql<number>`count(*)::int` }).from(supportTickets).groupBy(supportTickets.status),
|
||||
]);
|
||||
|
||||
for (const r of results) {
|
||||
if (r.status === "rejected") console.error("[admin/insights] query failed", r.reason);
|
||||
}
|
||||
|
||||
const [signupRows, recipeRows, tierRows, visibilityRows, usageRows, ticketRows] = results.map((r) =>
|
||||
r.status === "fulfilled" ? r.value : []
|
||||
) as [
|
||||
{ day: string; n: number }[],
|
||||
{ day: string; aiGenerated: boolean; n: number }[],
|
||||
{ tier: "free" | "pro" | "family"; n: number }[],
|
||||
{ visibility: "private" | "unlisted" | "public" | "followers"; n: number }[],
|
||||
{ month: string; n: number }[],
|
||||
{ status: "open" | "triaged" | "closed"; n: number }[],
|
||||
];
|
||||
|
||||
const signupByDay = new Map(signupRows.map((r) => [r.day, r.n]));
|
||||
const signupSeries = lastNDays(DAYS).map((day) => ({ date: day, value: signupByDay.get(day) ?? 0 }));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mirrors CHANGELOG.md at the repo root — update both together.
|
||||
export const APP_VERSION = "0.55.1";
|
||||
export const APP_VERSION = "0.55.2";
|
||||
|
||||
export type ChangelogEntry = {
|
||||
version: string;
|
||||
@@ -11,6 +11,13 @@ export type ChangelogEntry = {
|
||||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
version: "0.55.2",
|
||||
date: "2026-07-19 18:20",
|
||||
fixed: [
|
||||
"Admin Insights page could 500 the whole panel if any one of its six aggregate queries failed — replaced a non-standard positional GROUP BY with the same expression-repeat pattern used everywhere else in the codebase, and made the six queries independent (Promise.allSettled) so one failing degrades that one chart instead of crashing the page.",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.55.1",
|
||||
date: "2026-07-19 18:05",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@epicure/web",
|
||||
"version": "0.55.1",
|
||||
"version": "0.55.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
Reference in New Issue
Block a user