Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Release Notes
=============

Version 0.50.2
--------------

- More efficient key retrieval (#2804)
- fix dashboard card enrollment association and display (#2792)
- For published non-test_mode courses, only process contentfile archives of the best run (#2786)

Version 0.50.0 (Released December 11, 2025)
--------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ describe.each([
status: EnrollmentStatus.Completed,
mode: EnrollmentMode.Verified,
grades: [],
run: mitxonline.factories.courses.courseRun(),
}
renderWithProviders(
<DashboardCard
Expand Down Expand Up @@ -631,6 +632,7 @@ describe.each([
status: status,
mode: EnrollmentMode.Audit,
grades: [],
run: mitxonline.factories.courses.courseRun(),
}
renderWithProviders(
<DashboardCard titleAction="marketing" dashboardResource={course} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,8 @@ const UpgradeBanner: React.FC<
}

const CountdownRoot = styled.div(({ theme }) => ({
width: "142px",
marginRight: "32px",
width: "100%",
paddingRight: "32px",
display: "flex",
justifyContent: "center",
alignSelf: "end",
Expand Down Expand Up @@ -539,6 +539,10 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
const run = isCourse ? dashboardResource.run : undefined
const coursewareId = isCourse ? dashboardResource.coursewareId : null
const readableId = isCourse ? dashboardResource.readableId : null
const hasValidCertificate = isCourse ? !!run?.certificate?.link : false
const enrollmentStatus = hasValidCertificate
? EnrollmentStatus.Completed
: enrollment?.status

// Title link logic
const coursewareUrl = run?.coursewareUrl
Expand Down Expand Up @@ -599,9 +603,7 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
) : (
<TitleText>{title}</TitleText>
)}
{isCourse &&
enrollment?.status === EnrollmentStatus.Completed &&
run?.certificate?.link ? (
{isCourse && run?.certificate?.link ? (
<SubtitleLink href={run.certificate.link}>
<RiAwardLine size="16px" />
View Certificate
Expand All @@ -625,15 +627,15 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
) : isCourse ? (
<>
<EnrollmentStatusIndicator
status={enrollment?.status}
status={enrollmentStatus}
showNotComplete={showNotComplete}
/>
<CoursewareButton
data-testid="courseware-button"
coursewareId={coursewareId}
readableId={readableId}
startDate={run?.startDate}
enrollmentStatus={enrollment?.status}
enrollmentStatus={enrollmentStatus}
href={buttonHref ?? run?.coursewareUrl}
endDate={run?.endDate}
noun={noun}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
mitxonlineCourse,
mitxonlineProgram,
programEnrollmentsToPrograms,
selectBestEnrollment,
transformEnrollmentToDashboard,
userEnrollmentsToDashboardCourses,
} from "./transform"
import { DashboardCard } from "./DashboardCard"
Expand Down Expand Up @@ -249,10 +251,9 @@ interface ProgramEnrollmentDisplayProps {
const ProgramEnrollmentDisplay: React.FC<ProgramEnrollmentDisplayProps> = ({
programId,
}) => {
const { data: userCourses, isLoading: userEnrollmentsLoading } = useQuery({
...enrollmentQueries.courseRunEnrollmentsList(),
select: userEnrollmentsToDashboardCourses,
})
const { data: rawEnrollments, isLoading: userEnrollmentsLoading } = useQuery(
enrollmentQueries.courseRunEnrollmentsList(),
)
const { data: rawProgram, isLoading: programLoading } = useQuery(
programsQueries.programDetail({ id: programId }),
)
Expand All @@ -277,6 +278,19 @@ const ProgramEnrollmentDisplay: React.FC<ProgramEnrollmentDisplayProps> = ({
programEnrollmentsLoading ||
programCoursesLoading

// Group enrollments by course ID for efficient lookup
const enrollmentsByCourseId = (rawEnrollments || []).reduce(
(acc, enrollment) => {
const courseId = enrollment.run.course.id
if (!acc[courseId]) {
acc[courseId] = []
}
acc[courseId].push(enrollment)
return acc
},
{} as Record<number, typeof rawEnrollments>,
)

// Build sections from requirement tree
const requirementSections =
program?.reqTree
Expand All @@ -286,12 +300,30 @@ const ProgramEnrollmentDisplay: React.FC<ProgramEnrollmentDisplayProps> = ({
const sectionCourses = (rawProgramCourses?.results || [])
.filter((course) => courseIds.includes(course.id))
.map((course) => {
const enrollment = userCourses?.find((dashboardCourse) =>
course.courseruns.some(
(run) => run.courseware_id === dashboardCourse.coursewareId,
),
)?.enrollment
return mitxonlineCourse(course, enrollment)
// Find all enrollments for this course
const courseEnrollments = enrollmentsByCourseId[course.id] || []

if (courseEnrollments.length === 0) {
// No enrollment - use first run
return mitxonlineCourse(course)
}

// If multiple enrollments exist, select the best one
const bestEnrollment =
courseEnrollments.length > 1
? selectBestEnrollment(courseEnrollments)
: courseEnrollments[0]

// Find the matching run from course.courseruns
const matchingRun = course.courseruns.find(
(run) => run.id === bestEnrollment.run.id,
)

return mitxonlineCourse(
course,
transformEnrollmentToDashboard(bestEnrollment),
matchingRun,
)
})

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const dashboardCourse: PartialFactory<DashboardCourse> = (...overrides) => {
status: faker.helpers.arrayElement(Object.values(EnrollmentStatus)),
mode: faker.helpers.arrayElement(Object.values(EnrollmentMode)),
grades: [],
run: factories.courses.courseRun(),
},
},
...overrides,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
) ?? "",
},
grades: apiData.grades,
run: apiData.run,
},
} satisfies DashboardResource)
},
Expand Down Expand Up @@ -545,12 +546,13 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
})
})

test("selects enrollment with highest grade when multiple enrollments exist for same course", () => {
test("selects enrollment with highest grade when multiple enrollments exist for same course in same contract", () => {
const orgId = faker.number.int()
const contracts = createTestContracts(orgId, 1)
const contractId = contracts[0].id

// Create a course with 2 runs, both tied to the same contract
// (e.g., pilot run and soft launch run both in same contract)
const course = factories.courses.course({
id: 123,
title: "Test Course",
Expand All @@ -574,6 +576,7 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
grades: [factories.enrollment.grade({ grade: 0.65, passed: true })],
b2b_contract_id: contractId,
b2b_organization_id: orgId,
certificate: null,
})

const enrollmentHighGrade = factories.enrollment.courseEnrollment({
Expand All @@ -584,6 +587,7 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
grades: [factories.enrollment.grade({ grade: 0.95, passed: true })],
b2b_contract_id: contractId,
b2b_organization_id: orgId,
certificate: null,
})

const transformedCourses = organizationCoursesWithContracts({
Expand All @@ -601,6 +605,69 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
expect(transformedCourse.enrollment?.grades[0].grade).toBe(0.95)
expect(transformedCourse.enrollment?.id).toBe(enrollmentHighGrade.id)
})

test("prioritizes enrollment with certificate over grade when multiple enrollments exist", () => {
const orgId = faker.number.int()
const contracts = createTestContracts(orgId, 1)
const contractId = contracts[0].id

const course = factories.courses.course({
id: 123,
title: "Test Course",
})
const run1 = factories.courses.courseRun({
id: 1,
b2b_contract: contractId,
})
const run2 = factories.courses.courseRun({
id: 2,
b2b_contract: contractId,
})
course.courseruns = [run1, run2]

// Enrollment with higher grade but no certificate
const enrollmentHighGradeNoCert = factories.enrollment.courseEnrollment({
run: {
id: run1.id,
course: { id: course.id, title: course.title },
},
grades: [factories.enrollment.grade({ grade: 0.95, passed: true })],
b2b_contract_id: contractId,
b2b_organization_id: orgId,
certificate: null,
})

// Enrollment with lower/no grade but has certificate
const enrollmentWithCert = factories.enrollment.courseEnrollment({
run: {
id: run2.id,
course: { id: course.id, title: course.title },
},
grades: [],
b2b_contract_id: contractId,
b2b_organization_id: orgId,
certificate: {
uuid: "test-cert-uuid",
link: "/certificate/test-link/",
},
})

const transformedCourses = organizationCoursesWithContracts({
courses: [course],
contracts,
enrollments: [enrollmentHighGradeNoCert, enrollmentWithCert],
})

expect(transformedCourses).toHaveLength(1)
const transformedCourse = transformedCourses[0]

// Should select the enrollment with the certificate, even though it has no grade
expect(transformedCourse.enrollment).toBeDefined()
expect(transformedCourse.enrollment?.id).toBe(enrollmentWithCert.id)
expect(transformedCourse.enrollment?.certificate?.uuid).toBe(
"test-cert-uuid",
)
})
})

describe("transformEnrollmentToDashboard", () => {
Expand Down Expand Up @@ -629,6 +696,7 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
link: "/certificate/course/test123/",
},
grades: enrollment.grades,
run: enrollment.run,
})
})

Expand Down
Loading
Loading