perf(webapp): aggregate admin notification interaction counts in the database (#4616)
## Summary The notifications admin list loaded every interaction row for the notifications on the current page just to show three per-notification counters (seen, clicked, dismissed), then counted them in memory. On notifications with many interactions this made the page slow to load and heavy on memory, even though only 20 notifications are shown. ## Fix Compute the counters in a single grouped aggregate in the database instead, returning one row per notification rather than one row per interaction: ```sql SELECT "notificationId", COUNT(*) AS seen, COUNT(*) FILTER (WHERE "webappClickedAt" IS NOT NULL) AS clicked, COUNT(*) FILTER (WHERE "webappDismissedAt" IS NOT NULL OR "cliDismissedAt" IS NOT NULL) AS dismissed FROM "PlatformNotificationInteraction" WHERE "notificationId" IN (...) GROUP BY "notificationId" ``` Behavior is unchanged; notifications with no interactions report zero.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { z } from "zod";
|
||||
import { errAsync, fromPromise, type ResultAsync } from "neverthrow";
|
||||
import { prisma } from "~/db.server";
|
||||
import { Prisma, prisma, sqlDatabaseSchema } from "~/db.server";
|
||||
import {
|
||||
type PlatformNotificationScope,
|
||||
type PlatformNotificationSurface,
|
||||
@@ -49,25 +49,41 @@ export async function getAdminNotificationsList({
|
||||
orderBy: [{ createdAt: "desc" }],
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
include: {
|
||||
_count: {
|
||||
select: { interactions: true },
|
||||
},
|
||||
interactions: {
|
||||
select: {
|
||||
webappDismissedAt: true,
|
||||
webappClickedAt: true,
|
||||
cliDismissedAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.platformNotification.count({ where }),
|
||||
]);
|
||||
|
||||
const notificationIds = notifications.map((n) => n.id);
|
||||
|
||||
const interactionStats =
|
||||
notificationIds.length > 0
|
||||
? await prisma.$queryRaw<
|
||||
{
|
||||
notificationId: string;
|
||||
seen: bigint;
|
||||
clicked: bigint;
|
||||
dismissed: bigint;
|
||||
}[]
|
||||
>`
|
||||
SELECT
|
||||
"notificationId",
|
||||
COUNT(*) AS seen,
|
||||
COUNT(*) FILTER (WHERE "webappClickedAt" IS NOT NULL) AS clicked,
|
||||
COUNT(*) FILTER (
|
||||
WHERE "webappDismissedAt" IS NOT NULL OR "cliDismissedAt" IS NOT NULL
|
||||
) AS dismissed
|
||||
FROM ${sqlDatabaseSchema}."PlatformNotificationInteraction"
|
||||
WHERE "notificationId" IN (${Prisma.join(notificationIds)})
|
||||
GROUP BY "notificationId"
|
||||
`
|
||||
: [];
|
||||
|
||||
const statsById = new Map(interactionStats.map((row) => [row.notificationId, row]));
|
||||
|
||||
return {
|
||||
notifications: notifications.map((n) => {
|
||||
const parsed = PayloadV1Schema.safeParse(n.payload);
|
||||
const stats = statsById.get(n.id);
|
||||
return {
|
||||
id: n.id,
|
||||
friendlyId: n.friendlyId,
|
||||
@@ -99,11 +115,9 @@ export async function getAdminNotificationsList({
|
||||
cliMaxDaysAfterFirstSeen: n.cliMaxDaysAfterFirstSeen,
|
||||
cliShowEvery: n.cliShowEvery,
|
||||
stats: {
|
||||
seen: n._count.interactions,
|
||||
clicked: n.interactions.filter((i) => i.webappClickedAt !== null).length,
|
||||
dismissed: n.interactions.filter(
|
||||
(i) => i.webappDismissedAt !== null || i.cliDismissedAt !== null
|
||||
).length,
|
||||
seen: stats ? Number(stats.seen) : 0,
|
||||
clicked: stats ? Number(stats.clicked) : 0,
|
||||
dismissed: stats ? Number(stats.dismissed) : 0,
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user