Skip to content

Commit fe199f7

Browse files
authored
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.
1 parent c4b5e27 commit fe199f7

1 file changed

Lines changed: 32 additions & 18 deletions

File tree

apps/webapp/app/services/platformNotifications.server.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { z } from "zod";
22
import { errAsync, fromPromise, type ResultAsync } from "neverthrow";
3-
import { prisma } from "~/db.server";
3+
import { Prisma, prisma, sqlDatabaseSchema } from "~/db.server";
44
import {
55
type PlatformNotificationScope,
66
type PlatformNotificationSurface,
@@ -49,25 +49,41 @@ export async function getAdminNotificationsList({
4949
orderBy: [{ createdAt: "desc" }],
5050
skip: (page - 1) * pageSize,
5151
take: pageSize,
52-
include: {
53-
_count: {
54-
select: { interactions: true },
55-
},
56-
interactions: {
57-
select: {
58-
webappDismissedAt: true,
59-
webappClickedAt: true,
60-
cliDismissedAt: true,
61-
},
62-
},
63-
},
6452
}),
6553
prisma.platformNotification.count({ where }),
6654
]);
6755

56+
const notificationIds = notifications.map((n) => n.id);
57+
58+
const interactionStats =
59+
notificationIds.length > 0
60+
? await prisma.$queryRaw<
61+
{
62+
notificationId: string;
63+
seen: bigint;
64+
clicked: bigint;
65+
dismissed: bigint;
66+
}[]
67+
>`
68+
SELECT
69+
"notificationId",
70+
COUNT(*) AS seen,
71+
COUNT(*) FILTER (WHERE "webappClickedAt" IS NOT NULL) AS clicked,
72+
COUNT(*) FILTER (
73+
WHERE "webappDismissedAt" IS NOT NULL OR "cliDismissedAt" IS NOT NULL
74+
) AS dismissed
75+
FROM ${sqlDatabaseSchema}."PlatformNotificationInteraction"
76+
WHERE "notificationId" IN (${Prisma.join(notificationIds)})
77+
GROUP BY "notificationId"
78+
`
79+
: [];
80+
81+
const statsById = new Map(interactionStats.map((row) => [row.notificationId, row]));
82+
6883
return {
6984
notifications: notifications.map((n) => {
7085
const parsed = PayloadV1Schema.safeParse(n.payload);
86+
const stats = statsById.get(n.id);
7187
return {
7288
id: n.id,
7389
friendlyId: n.friendlyId,
@@ -99,11 +115,9 @@ export async function getAdminNotificationsList({
99115
cliMaxDaysAfterFirstSeen: n.cliMaxDaysAfterFirstSeen,
100116
cliShowEvery: n.cliShowEvery,
101117
stats: {
102-
seen: n._count.interactions,
103-
clicked: n.interactions.filter((i) => i.webappClickedAt !== null).length,
104-
dismissed: n.interactions.filter(
105-
(i) => i.webappDismissedAt !== null || i.cliDismissedAt !== null
106-
).length,
118+
seen: stats ? Number(stats.seen) : 0,
119+
clicked: stats ? Number(stats.clicked) : 0,
120+
dismissed: stats ? Number(stats.dismissed) : 0,
107121
},
108122
};
109123
}),

0 commit comments

Comments
 (0)