Skip to content

Commit ea19a15

Browse files
HugoGresseclaude
andcommitted
Fix null createdAt crash in Feedback vote results after Gen2 trigger migration
The Gen1->Gen2 Firestore trigger migration (#1664) made the vote aggregation fire before serverTimestamp() resolves, writing null createdAt/updatedAt into aggregated sessionVotes docs. The Feedback app then crashed on `value2.createdAt.toDate()` with "TypeError: can't access property toDate, h.createdAt is null". - aggregateVotes.ts: normalizeVoteTimestamps() falls back to the snapshot's server-assigned createTime/updateTime when the field is null, so new aggregated docs never store a null timestamp. - getVoteResultSelectorSelector.js: guard the .toDate() calls and skip entries with no usable date, so already-corrupted prod docs no longer crash the whole results view. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aaca0bd commit ea19a15

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

functions/src/triggers/aggregateVotes.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const aggregateVotesCreate = onDocumentCreated(
1414
}
1515
return incrementVoteAggregate(
1616
admin.firestore(),
17-
new Vote(snapshot.id, snapshot.data() as VoteData)
17+
new Vote(snapshot.id, normalizeVoteTimestamps(snapshot))
1818
)
1919
}
2020
)
@@ -28,11 +28,27 @@ export const aggregateVotesUpdate = onDocumentUpdated(
2828
}
2929
return incrementVoteAggregate(
3030
admin.firestore(),
31-
new Vote(change.after.id, change.after.data() as VoteData)
31+
new Vote(change.after.id, normalizeVoteTimestamps(change.after))
3232
)
3333
}
3434
)
3535

36+
// Gen2 (Eventarc) triggers can fire before a serverTimestamp() sentinel
37+
// resolves, leaving createdAt/updatedAt null in the snapshot. The Gen1 API
38+
// hid that intermediate write. Fall back to the snapshot's server-assigned
39+
// createTime/updateTime so the aggregated sessionVotes doc never stores a
40+
// null timestamp (which crashes the Feedback app's .toDate() calls).
41+
const normalizeVoteTimestamps = (
42+
snapshot: FirebaseFirestore.DocumentSnapshot
43+
): VoteData => {
44+
const data = snapshot.data() as VoteData
45+
return {
46+
...data,
47+
createdAt: data.createdAt ?? snapshot.createTime ?? snapshot.updateTime,
48+
updatedAt: data.updatedAt ?? snapshot.updateTime ?? snapshot.createTime,
49+
}
50+
}
51+
3652
export const incrementVoteAggregate = (
3753
firestoreDb: FirebaseFirestore.Firestore,
3854
vote: Vote

src/feedback/talk/core/getVoteResultSelectorSelector.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,23 @@ export const getVoteResultSelectorSelector = createSelector(
6262
// Empty object due to deletion
6363
return
6464
}
65+
// A regression (Gen2 trigger migration) wrote some
66+
// aggregated votes with null createdAt/updatedAt. Guard
67+
// the .toDate() calls so one bad entry can't crash the
68+
// whole results view; fall back to whichever date exists.
69+
const updatedAt =
70+
value2.updatedAt?.toDate?.() ??
71+
value2.createdAt?.toDate?.() ??
72+
null
73+
const createdAt = value2.createdAt?.toDate?.() ?? updatedAt
74+
if (!updatedAt) {
75+
// No usable timestamp, skip this corrupted entry
76+
return
77+
}
6578
transformResult[key].push({
6679
...value2,
67-
updatedAt: value2.updatedAt.toDate(),
68-
createdAt: value2.createdAt.toDate(),
80+
updatedAt,
81+
createdAt,
6982
})
7083
})
7184
transformResult[key] = transformResult[key].sort(

0 commit comments

Comments
 (0)