fix notifications #126

Merged
chrisp merged 2 commits from claude/wimbledon-bracket-announcement-bug-tg1wzl into main 2026-07-03 15:47:56 +00:00
2 changed files with 51 additions and 17 deletions
Showing only changes of commit 5be8ac3f2f - Show all commits

View file

@ -373,6 +373,29 @@ describe("notifyQualifyingPointsUpdate", () => {
expect(call.eliminated).toEqual([]);
});
it("short-circuits before the QP-total/season lookups when nothing drafted is involved", async () => {
// A knockout occurred but the player isn't drafted in any league. The notifier
// is invoked (the sync fires it on any elimination) but must not run the rest
// of its query battery just to send nothing.
const db = makeDb({
draftPicks: { findMany: vi.fn().mockResolvedValue([]) },
});
await notifyQualifyingPointsUpdate(
SPORTS_SEASON_ID,
SCORING_EVENT_ID,
db as never,
new Set(["p-nonexistent"]),
new Set(["p-undrafted"])
);
expect(db.query.draftPicks.findMany).toHaveBeenCalledOnce();
expect(db.query.seasonParticipantQualifyingTotals.findMany).not.toHaveBeenCalled();
expect(db.query.seasons.findMany).not.toHaveBeenCalled();
expect(db.query.seasonParticipants.findMany).not.toHaveBeenCalled();
expect(sendQualifyingPointsUpdateNotification).not.toHaveBeenCalled();
});
it("fires when a drafted player is knocked out even though no QP changed", async () => {
const db = makeDbWithEliminated();

View file

@ -59,6 +59,34 @@ export async function notifyQualifyingPointsUpdate(
if (qpEarnedById.size === 0 && eliminatedIds.size === 0) return;
const seasonIds = seasonSports.map((s) => s.seasonId);
// Batch-fetch all draft picks for all leagues at once, grouped by season.
// Fetched before the remaining lookups so we can short-circuit when nothing
// that happened this sync was actually drafted: during a Grand Slam's early
// rounds most eliminated players are undrafted, and the notifier now fires on
// every sync that has any elimination — no point running the rest of the
// queries just to send nothing.
const allPicks = await db.query.draftPicks.findMany({
where: inArray(schema.draftPicks.seasonId, seasonIds),
with: { team: true },
});
const picksBySeasonId = new Map<string, (typeof allPicks)[number][]>();
const draftedParticipantIds = new Set<string>();
for (const pick of allPicks) {
draftedParticipantIds.add(pick.participantId);
const bucket = picksBySeasonId.get(pick.seasonId);
if (bucket) {
bucket.push(pick);
} else {
picksBySeasonId.set(pick.seasonId, [pick]);
}
}
const hasDraftedQP = [...qpEarnedById.keys()].some((id) => draftedParticipantIds.has(id));
const hasDraftedEliminated = [...eliminatedIds].some((id) => draftedParticipantIds.has(id));
if (!hasDraftedQP && !hasDraftedEliminated) return;
// Current running QP totals for the season
const qpTotals = await db.query.seasonParticipantQualifyingTotals.findMany({
where: eq(schema.seasonParticipantQualifyingTotals.sportsSeasonId, sportsSeasonId),
@ -67,8 +95,6 @@ export async function notifyQualifyingPointsUpdate(
qpTotals.map((t) => [t.participantId, parseFloat(t.totalQualifyingPoints)])
);
const seasonIds = seasonSports.map((s) => s.seasonId);
// Batch-fetch all season + league metadata in one query
const seasons = await db.query.seasons.findMany({
where: inArray(schema.seasons.id, seasonIds),
@ -76,21 +102,6 @@ export async function notifyQualifyingPointsUpdate(
});
const seasonMap = new Map(seasons.map((s) => [s.id, s]));
// Batch-fetch all draft picks for all leagues at once, grouped by season
const allPicks = await db.query.draftPicks.findMany({
where: inArray(schema.draftPicks.seasonId, seasonIds),
with: { team: true },
});
const picksBySeasonId = new Map<string, (typeof allPicks)[number][]>();
for (const pick of allPicks) {
const bucket = picksBySeasonId.get(pick.seasonId);
if (bucket) {
bucket.push(pick);
} else {
picksBySeasonId.set(pick.seasonId, [pick]);
}
}
// Batch-fetch participant display names once (same participants across all leagues)
const allParticipantIds = [...new Set([...qpEarnedById.keys(), ...eliminatedIds])];
const participants = await db.query.seasonParticipants.findMany({