"program as course" display on learner dashboard#3073
Conversation
There was a problem hiding this comment.
Pull request overview
Implements the “program as course” dashboard presentation (for programs with display_mode: "course") and updates the dashboard card UI to match the new designs, including new card components and supporting helpers/tests.
Changes:
- Added
ProgramAsCourseCard+ProgressBadgeto render a program as a stacked “course-like” card with module list and progress/dates UI. - Extended dashboard helpers and enrollment display logic to compute/render program-as-course enrollments on both the dashboard home and within program requirement sections.
- Introduced a
ModuleCardvariant for stacked module display and added/updated Jest/RTL tests for the new behaviors.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/helpers.ts | Adds helper functions to compute enrollment status for course runs and program enrollments. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/helpers.test.tsx | Adds unit coverage for the new helper functions. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgressBadge.tsx | New UI component for displaying progress state (“Not Started”/“In Progress”/“Completed”). |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx | New composite card to display a program as a course with module list and date popover. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.test.tsx | Adds RTL tests for rendering and the date popover interaction. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ModuleCard.tsx | New/updated card implementation used to render stacked module cards inside ProgramAsCourseCard. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx | Integrates program-as-course rendering into dashboard home + program requirement sections and adds supporting queries/mappings. |
| frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.test.tsx | Adds coverage for program-as-course rendering on dashboard home and within program requirements. |
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx
Outdated
Show resolved
Hide resolved
| return sum + parseInt(section.node.data.operator_value, 10) | ||
| } | ||
| return sum + section.courses.length | ||
| return sum + section.items.length |
There was a problem hiding this comment.
completedCount only counts items where resourceType === "course", but totalCount sums section.items.length (which now includes required programs and program-as-course items). This makes the "{completedCount} of {totalCount} courses" progress summary incorrect when program requirements are present. Either count completion/total consistently across all item types (and update the label accordingly), or restrict totalCount to course-only items to match completedCount.
| return sum + section.items.length | |
| const courseItemsCount = section.items.filter( | |
| (item) => item.resourceType === "course", | |
| ).length | |
| return sum + courseItemsCount |
| @@ -414,7 +607,7 @@ const ProgramEnrollmentDisplay: React.FC<ProgramEnrollmentDisplayProps> = ({ | |||
| section.node.data.operator === "min_number_of" && | |||
| section.node.data.operator_value | |||
| ? parseInt(section.node.data.operator_value, 10) | |||
| : section.courses.length | |||
| : section.items.length | |||
|
|
|||
There was a problem hiding this comment.
Section progress uses sectionCompletedCount computed from course-only items, but sectionRequiredCount falls back to section.items.length (including programs). This will misreport progress (e.g., "Completed 0 of 2" when the section has 1 course + 1 required program). Align both counts to the same set of requirement types, or compute separate counts per type.
There was a problem hiding this comment.
Copilot's observation is true, though it is assumed that ... Program as Course will only have course children
@gumaerc I think you've added some comments around this (I will look more) but if we don't have one, a docstring for ProgramAsCourseCard would be worthwhile mentioning this.
There was a problem hiding this comment.
I had added a docstring but added a clarification about this, thanks.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx
Outdated
Show resolved
Hide resolved
ChristopherChudzicki
left a comment
There was a problem hiding this comment.
This looks good. Am noticing a few issues, and will still look at the code a bit more.
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx
Show resolved
Hide resolved
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx
Outdated
Show resolved
Hide resolved
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ProgramAsCourseCard.tsx
Outdated
Show resolved
Hide resolved
frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/EnrollmentDisplay.tsx
Show resolved
Hide resolved
… that are its children
…s relating to "program as course" to make things more clear
4129e0f to
9230d5c
Compare
| function getIdsFromReqTree( | ||
| nodes: V2ProgramRequirement[], | ||
| type?: ReqTreeResourceType, | ||
| ): ReqTreeResourceItem[] | number[] { | ||
| const resources = getReqTreeResources(nodes) | ||
| if (!type) return resources | ||
|
|
||
| return resources | ||
| .filter((resource) => resource.type === type) | ||
| .map((resource) => resource.id) | ||
| } |
There was a problem hiding this comment.
Bug: The function getIdsFromReqTree is declared twice with conflicting signatures. The const declaration on line 178 overwrites the overloaded function, causing new calls expecting an array to fail.
Severity: CRITICAL
Suggested Fix
Rename one of the getIdsFromReqTree functions to resolve the naming conflict. The new overloaded function (lines 157-172) should have a unique name, and all new call sites in EnrollmentDisplay.tsx should be updated to use this new name. This will ensure that both the new and existing logic call the correct function implementations.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: frontends/main/src/common/mitxonline.ts#L157-L172
Potential issue: The file `frontends/main/src/common/mitxonline.ts` contains two
conflicting declarations for `getIdsFromReqTree`. An overloaded function is defined
(lines 157-172), but it is immediately overwritten by a `const` assignment with a
different implementation and return type (lines 178-200). New usages in
`EnrollmentDisplay.tsx` call the function expecting an array (`number[]` or
`ReqTreeResourceItem[]`) as per the new overloads. However, at runtime, they will
receive an object (`{ courseIds: number[], programIds: number[] }`), leading to
`TypeError` exceptions when attempting to iterate over the result (e.g., with `new
Set()` or `.map()`). This will cause a runtime crash when rendering program enrollments.

What are the relevant tickets?
Closes https://github.com/mitodl/hq/issues/10521
Description (What does it do?)
This PR implements some design changes shown in the wireframes in the issue above. Support for displaying programs with
display_mode: "course"was added.Screenshots (if appropriate):
How can this be tested?
display_mode: "courseDashboardCardin any conceivable scenario, checking what's rendered against the Figma wireframes in the issue above