Fix verified enrollment for program-as-course hierarchy#3100
Conversation
The API changed from program_id path param to request_body array. Update DashboardCard and the enrollment test to use the new signature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…archy
ModuleCard now accepts ancestorPrograms (array of {readable_id, enrollment_mode})
instead of a single programEnrollment. When any ancestor has verified
enrollment_mode, it calls createVerifiedProgramEnrollment with all ancestor
readable_ids. This enables verified enrollment for courses nested inside a
program-as-course whose grandparent program has the verified enrollment.
ProgramAsCourseCard passes ancestorPrograms through to ModuleCard.
EnrollmentDisplay assembles the array: home dashboard passes the parent
program-as-course; program dashboard passes both parent and grandparent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e, fix legacy test - Extract renderResource() in EnrollmentExpandCollapse to eliminate duplicated map callback between shown/hidden resource lists. This also fixes the missing ancestorPrograms prop in the hidden resources path. - Move AncestorProgram type from ModuleCard to helpers.ts (shared domain concept, not card-specific). - Fix DashboardCard.test.tsx verified enrollment URL to match new API signature (courserun_id only, no program_id in path). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use factory defaults instead of hardcoded titles and spreading throwaway factory instances. Assert on the factory-generated values instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the hand-rolled EnrollmentMode const in ModuleCard with EnrollmentModeEnum from @mitodl/mitxonline-api-axios. Use the same type for AncestorProgram.enrollment_mode in helpers.ts for type safety. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d verified flag Replace ancestorPrograms array with a cleaner design: - ProgramAsCourseCard accepts optional ancestorProgramEnrollment (the grandparent enrollment, singular) and assembles parentProgramIds + useVerifiedEnrollment from courseProgram.readable_id + ancestor - ModuleCard accepts simple parentProgramIds (string[]) and useVerifiedEnrollment (boolean) — no enrollment objects needed - Remove AncestorProgram type from helpers.ts (no longer needed) This fixes the bug where ancestorPrograms was only populated when courseProgramEnrollment existed — the exact case we don't need it. Now the parent readable_id always comes from courseProgram (the program detail object), which exists regardless of enrollment status. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-generate program and module IDs inside setupCardData instead of requiring callers to pass arbitrary values. Assert on factory-generated values (cardData.courseProgram.title, cardData.moduleCourses[0].title) instead of hardcoded strings. Add docstring to setupCardData. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eq_tree The test verifies display order follows req_tree, not the moduleCourses array order. Reversing the moduleCourses input is simpler and more directly tests the behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…des, use invariant - Add integration test for grandparent verified enrollment flowing through EnrollmentDisplay -> ProgramAsCourseCard -> ModuleCard, asserting both parent and grandparent readable_ids in POST body - Split verified enrollment test into two: regular course (one program ID) and program-as-course module (two program IDs) - Extract setupProgramDashboardVerifiedEnrollmentScenario helper (API setup only, no render) - Remove unnecessary grades: [] factory override in ProgramAsCourseCard tests - Remove hardcoded IDs and titles in EnrollmentDisplay verified enrollment test - Replace card\! non-null assertions with invariant() for clearer failure messages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace local EnrollmentMode constant with the shared isVerifiedEnrollmentMode helper from @/common/mitxonline, consistent with ModuleCard and ProgramAsCourseCard. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes one-click verified enrollment behavior for “program-as-course” hierarchies on the dashboard by ensuring the verified enrollment API receives the full parent/ancestor program chain, and refactors related dashboard card plumbing/tests accordingly.
Changes:
- Pass ancestor program enrollment context down to program-as-course module cards and convert it into
parentProgramIds+useVerifiedEnrollment. - Update verified program enrollment API usage to the new signature (
request_body: string[]) and new URL shape (courserun-only). - Refactor dashboard rendering paths and strengthen tests around verified enrollment scenarios.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| yarn.lock | Switches @mitodl/mitxonline-api-axios resolution to a GitHub tarball source. |
| frontends/main/package.json | Pins @mitodl/mitxonline-api-axios to a GitHub tarball URL. |
| frontends/api/package.json | Pins @mitodl/mitxonline-api-axios to a GitHub tarball URL. |
| frontends/api/src/mitxonline/test-utils/urls.ts | Updates verified enrollment endpoint helper to courserun-only URL. |
| frontends/main/src/common/mitxonline.ts | Adds isVerifiedEnrollmentMode helper. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx | Adds ancestor enrollment support and passes parentProgramIds/useVerifiedEnrollment to module cards. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ModuleCard.tsx | Switches verified enrollment to request_body: string[] and threads new props through enrollment handler. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx | Passes ancestor enrollment on program dashboard and refactors rendering; updates module-id derivation. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx | Updates verified enrollment mutation to the new request shape; uses isVerifiedEnrollmentMode. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/*.test.tsx | Updates/extends tests for verified enrollment chain behavior and removes hardcoded test data. |
There was a problem hiding this comment.
I was able to create a verified program enrollment and subsequently enroll in modules in my "course-like-program." I didn't notice anything super worrying in the code, just a few small things we could look at before merging. One of them is not included in your changes, but maybe something we should change. In the enrollment hooks at frontends/api/src/mitxonline/hooks/enrollment.index.ts we have:
const useCreateVerifiedProgramEnrollment = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (
opts: VerifiedProgramEnrollmentsApiVerifiedProgramEnrollmentsCreateRequest,
) => verifiedProgramEnrollmentsApi.verifiedProgramEnrollmentsCreate(opts),
onSettled: () => {
queryClient.invalidateQueries({
queryKey: enrollmentKeys.courseRunEnrollmentsList(),
})
},
})
}This only invalidates the course run enrollments list, but now that we're displaying programs as part of other programs, maybe we should invalidate the program enrollment list as well?
We could also address the linting warning below:
| enabled: Boolean(enrolledInProgram && requiredProgramIds.length > 0), | ||
| }) | ||
|
|
||
| const requiredProgramList = requiredPrograms?.results ?? [] |
There was a problem hiding this comment.
The warnings below don't seem that critical, but could cause unnecessary re-rendering so maybe we should memoize requiredProgramList?
There was a problem hiding this comment.
Honestly, I think this is more an indicator that we were over-eager in using useMemo and useCallback. They generally aren't relevant unless you have child components using React.memo, which ours usually don't.
What are the relevant tickets?
Description (What does it do?)
Fixes verified enrollment for courses nested inside a "program-as-course" (aka courselike program, crogram).
Problem: When a user has a verified enrollment in a grandparent program (e.g.,
program-v1:UAI+B2C) but no enrollment yet in a child courselike program (program-v1:UAI+B2C.1), clicking "Start Course" on a grandchild module was opening the enrollment dialog instead of using the verified enrollment API. The backend needs both the parent and grandparent program IDs to create the enrollment chain.Solution:
verifiedProgramEnrollmentsAPI call to use the new signature (request_body: string[]instead ofprogram_id), per Release 1.143.2 mitxonline#3420ProgramAsCourseCardnow accepts an optionalancestorProgramEnrollmentprop (the grandparent's enrollment) and assemblesparentProgramIdsfromcourseProgram.readable_id+ the ancestor'sreadable_idModuleCardreceives simpleparentProgramIds: string[]anduseVerifiedEnrollment: boolean— it doesn't need to know about enrollment objectsEnrollmentDisplaypasses the grandparent's enrollment asancestorProgramEnrollmenton the program dashboardAlso includes:
EnrollmentExpandCollapseviarenderResourceextractionEnrollmentModeconstants withisVerifiedEnrollmentModehelper across bothModuleCardandDashboardCardinvariant()instead of non-null assertions, minimal factory overridesHow can this be tested?