Task #644 Refactor create and edit assignemnts functions#20
Task #644 Refactor create and edit assignemnts functions#20rolivares93 wants to merge 15 commits intomainfrom
Conversation
|
We need a way to emulate a complete atomic transaction. In other words, we need to guarantee that all users were assigned the assignment successfully. The current approach can fail for some users, while the rest would have the assignment. We want to avoid that. This also uses the previous approach of background triggers. We do not want eventual consistency. All users should be able to log in and play the assignment immediately once the admin creates it. |
…participants receive the assignment
5b98fde to
0ee73ee
Compare
|
Since this is a big PR and takes time to review, I'm gonna hold off until I get a response to the comments above. |
…onAssignments to createAssignments and updateAssignments respectively
| administrationData, | ||
| transaction | ||
| orgChunk, | ||
| administrationData |
There was a problem hiding this comment.
Do you think the rollbackAssignmentCreation needs to be chunked as well since the db supports some MAX_TRANSACTIONS? If not, I suggest we just simply pass usersToUpdate since we tested that rollbackAssignmentCreation does not affect users that have not been created. So it is safe to pass userIds that have no entries.
| // If remainingUsersToRemove.length === 0, then these chunks will be of zero length | ||
| // and the entire loop below is a no-op. | ||
| for (const _userChunk of _chunk(remainingUsers, MAX_TRANSACTIONS)) { | ||
| await db.runTransaction(async (transaction) => { |
There was a problem hiding this comment.
Also I am not in favor of this pattern. What is going to happen here is, we are going to do sequential updates. Meaning we will update a set of users; wait and then update the next set of users, that adds time additively. Do you think firebase supports parallel requests (at least we should try it out)? In that case what I suggest pushing all the updateAssignmentForUsers and other async functions in an array and run Promise.all(array). That way the requests will be fired together and be faster.
|
|
||
| if (mode === "add") { | ||
| totalUsersAssigned = usersToUpdate.length; | ||
| createdUserIds.push(...usersToUpdate); |
There was a problem hiding this comment.
Is there a reason we need to create a copy of usersToUpdate? Can't we simply pass that?
| try { | ||
| for (let i = 0; i < orgChunks.length; i++) { | ||
| const orgChunk = orgChunks[i]; | ||
| const result = await updateAssignmentsForOrgChunkHandler({ |
There was a problem hiding this comment.
We should follow the same Promise.all pattern here as well if we have verified that it works for the updateAssignmentsForOrgChunkHandler function
| }); | ||
|
|
||
| for (const _userChunk of _chunk(remainingUsersToRemove, MAX_TRANSACTIONS)) { | ||
| await db.runTransaction(async (transaction) => { |
There was a problem hiding this comment.
Same promise.all here as well
|
|
||
| for (let i = 0; i < orgChunks.length; i++) { | ||
| const orgChunk = orgChunks[i]; | ||
| const result = await updateAssignmentsForOrgChunkHandler({ |
There was a problem hiding this comment.
Same Promise.all here as well.
| if (orgChunk && administrationData && userIds.length > 0) { | ||
| try { | ||
| const { updateAdministrationStatsForOrgChunk } = await import( | ||
| "./on-assignment-updates.js" |
There was a problem hiding this comment.
Try to move this to the top as well
|
CREATING ASSIGNMENT CREATING.ASSIGNMENT.2025-Dec-17-2.movVIEWING ASSIGNMENT VIEWING.ASSIGNMENT.2025-Dec-17-2.mov |
Zio-4
left a comment
There was a problem hiding this comment.
It's hard to understand what a lot of the code is doing without going deep into the details like the "standardizeAdministrations" function for example.
Could you comments throughout the functions to make it clear what's happening like what the function is actually doing?
I was originally hoping we could completely refactor the create / edit assignment functions, but this will do for now.
| }); | ||
| } | ||
|
|
||
| return { success: false, userIds: createdUserIds, error }; |
There was a problem hiding this comment.
If we're going to do this, then we need to update the frontend to check for this.
My take, since we're doing it atomically, all or nothing, we should just throw an error and then the frontend can say that user creation failed. Because there's no partial success, so we're not going to be showing rows of users that failed.
| if (usersToRemove.length !== 0) { | ||
| if (usersToRemove.length <= MAX_TRANSACTIONS) { | ||
| return removeOrgsFromAssignments( | ||
| usersToRemove, | ||
| [administrationId], | ||
| removedExhaustiveOrgs, | ||
| transaction | ||
| ); | ||
| } else { | ||
| remainingUsersToRemove = usersToRemove; | ||
| return Promise.resolve(usersToRemove.length); | ||
| } | ||
| } else { | ||
| return Promise.resolve(0); | ||
| } |
There was a problem hiding this comment.
What is this section of code doing? Why are we resolving with the usersToRemove and then or resolving with zero? I think this should be rewritten to be more readable.
| if (!creatorUid) { | ||
| logger.error( | ||
| `Cannot sync assignments: administration ${newAdministrationId} has no createdBy field`, | ||
| { administrationId: newAdministrationId } | ||
| ); | ||
| throw new HttpsError( | ||
| "internal", | ||
| "Administration document is missing creator information" | ||
| ); |
There was a problem hiding this comment.
This shouldn't be possible because the creatorId will always be on the auth token when we first create the administration document, so it could never be missing it.
| if (!creatorUid) { | ||
| logger.error( | ||
| `Cannot sync assignments: administration ${newAdministrationId} has no createdBy field`, | ||
| { administrationId: newAdministrationId } | ||
| ); | ||
| throw new HttpsError( | ||
| "internal", | ||
| "Administration document is missing creator information" | ||
| ); | ||
| } |
| // If this is a new administration and sync failed, the error was already thrown | ||
| // and should have triggered rollback. Re-throw to prevent the function from | ||
| // returning successfully when assignments failed to be created. | ||
| const isNewAdministration = !administrationId; |
There was a problem hiding this comment.
Let's move this variable up a scope if we're going to reuse it here so we don't re-declare the same variables.
|
Also important issues that Codex caught:
|
Proposed changes
I refactored the functions responsible for the assignment creation to avoid the delay caused by firebase background functions.
Screen.Recording.2025-11-05.at.10.53.55.mov
Types of changes
What types of changes does this pull request introduce?
Additional Notes