Skip to content
Merged
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
12 changes: 12 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
Release Notes
=============

Version 0.68.0
--------------

- fix open textbooks (#3337)
- youtube etl should not unpublish ovs (#3334)
- dashboard refactor stage 2 (#3330)
- Adds a feature flag to link ocw course urls to corresponding learn urls (/courses/o/*) (#3263)
- Min score for vector learning resources endpoint (#3285)
- Update dependency granian to v2.7.4 [SECURITY] (#3309)
- Update dependency litellm to v1.83.10 [SECURITY] (#3331)
- Update dependency urllib3 to v2.7.0 [SECURITY] (#3329)

Version 0.67.2 (Released May 12, 2026)
--------------

Expand Down
18 changes: 18 additions & 0 deletions frontends/api/src/generated/v0/api.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 41 additions & 76 deletions frontends/main/src/app-pages/DashboardPage/ContractContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,9 @@ import { ErrorContent } from "../ErrorPage/ErrorPageTemplate"
import { matchOrganizationBySlug } from "@/common/utils"
import { ResourceType, getKey } from "./CoursewareDisplay/helpers"
import {
getCourseRunForSelectedLanguage,
getDistinctLanguageOptions,
getResolvedRunForSelectedLanguage,
getSelectedLanguageOption,
selectBestContractEnrollmentForLanguage,
} from "./CoursewareDisplay/languageOptions"
getDistinctDashboardLanguageOptions,
resolveSlotForLanguage,
} from "./CoursewareDisplay/model/dashboardViewModel"
import UnstyledRawHTML from "@/components/UnstyledRawHTML/UnstyledRawHTML"

const HeaderRoot = styled.div(({ theme }) => ({
Expand Down Expand Up @@ -398,54 +395,36 @@ const OrgProgramCollectionDisplay: React.FC<{
enrollments?.filter(
(enrollment) => enrollment.b2b_contract_id === contract.id,
) ?? []
// Prefer the user's existing enrollment for the selected language
// over the next/best run, so older-run enrollments stay visible
// when the contract surfaces a newer run.
const selectedLanguageEnrollment =
selectBestContractEnrollmentForLanguage(
course,
contractEnrollments,
selectedLanguageKey,
)
const selectedLanguageOption = getSelectedLanguageOption(
const { displayedEnrollment, displayedRun } = resolveSlotForLanguage(
course,
contractEnrollments,
selectedLanguageKey,
{ contractId: contract.id },
)
const selectedRun = selectedLanguageEnrollment
? ((course.courseruns ?? []).find(
(r) => r.id === selectedLanguageEnrollment.run.id,
) ?? null)
: getCourseRunForSelectedLanguage(course, selectedLanguageKey)
const resolvedRun = getResolvedRunForSelectedLanguage(
course,
selectedLanguageOption,
selectedRun,
selectedLanguageEnrollment,
contract.id,
)

const resource = displayedEnrollment
? {
type: DashboardType.CourseRunEnrollment,
data: displayedEnrollment,
}
: { type: DashboardType.Course, data: course }

return (
<DashboardCardStyled
Component="li"
key={getKey({
resourceType: ResourceType.Course,
id: course.id,
runId: selectedLanguageEnrollment?.run.id ?? resolvedRun?.id,
runId: displayedEnrollment?.run.id ?? displayedRun?.id,
})}
resource={
selectedLanguageEnrollment
? {
type: DashboardType.CourseRunEnrollment,
data: selectedLanguageEnrollment,
}
: { type: DashboardType.Course, data: course }
}
resource={resource}
noun="Module"
offerUpgrade={false}
buttonHref={
selectedLanguageEnrollment?.run.courseware_url ??
resolvedRun?.courseware_url
displayedEnrollment?.run.courseware_url ??
displayedRun?.courseware_url
}
selectedCourseRun={resolvedRun}
selectedCourseRun={displayedRun}
contractId={contract.id}
/>
)
Expand Down Expand Up @@ -526,56 +505,37 @@ const OrgProgramDisplay: React.FC<{
courseRunEnrollments?.filter(
(enrollment) => enrollment.b2b_contract_id === contract?.id,
) ?? []
// Prefer the user's existing enrollment for the selected
// language over the next/best run, so older-run enrollments
// stay visible when the contract surfaces a newer run.
const selectedLanguageEnrollment =
selectBestContractEnrollmentForLanguage(
const { displayedEnrollment, displayedRun } =
resolveSlotForLanguage(
course,
contractEnrollments,
selectedLanguageKey,
{ contractId: contract?.id },
)
const selectedLanguageOption = getSelectedLanguageOption(
course,
selectedLanguageKey,
)
const selectedRun = selectedLanguageEnrollment
? ((course.courseruns ?? []).find(
(r) => r.id === selectedLanguageEnrollment.run.id,
) ?? null)
: getCourseRunForSelectedLanguage(course, selectedLanguageKey)
const resolvedRun = getResolvedRunForSelectedLanguage(
course,
selectedLanguageOption,
selectedRun,
selectedLanguageEnrollment,
contract?.id,
)

const resource = displayedEnrollment
? {
type: DashboardType.CourseRunEnrollment,
data: displayedEnrollment,
}
: { type: DashboardType.Course, data: course }

return (
<DashboardCardStyled
Component="li"
key={getKey({
resourceType: ResourceType.Course,
id: course.id,
runId:
selectedLanguageEnrollment?.run.id ?? resolvedRun?.id,
runId: displayedEnrollment?.run.id ?? displayedRun?.id,
})}
resource={
selectedLanguageEnrollment
? {
type: DashboardType.CourseRunEnrollment,
data: selectedLanguageEnrollment,
}
: { type: DashboardType.Course, data: course }
}
resource={resource}
noun="Module"
offerUpgrade={false}
buttonHref={
selectedLanguageEnrollment?.run.courseware_url ??
resolvedRun?.courseware_url
displayedEnrollment?.run.courseware_url ??
displayedRun?.courseware_url
}
selectedCourseRun={resolvedRun}
selectedCourseRun={displayedRun}
contractId={contract?.id}
/>
)
Expand Down Expand Up @@ -644,8 +604,13 @@ const ContractContentInternal: React.FC<ContractContentInternalProps> = ({
[coursesQuery.data?.results],
)
const languageOptions = React.useMemo(
() => getDistinctLanguageOptions(contractCourses),
[contractCourses],
() =>
getDistinctDashboardLanguageOptions(
contractCourses,
courseRunEnrollmentsQuery.data ?? [],
{ contractId: contract.id },
),
[contract.id, contractCourses, courseRunEnrollmentsQuery.data],
)
const [selectedLanguageKey, setSelectedLanguageKey] = React.useState("")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,16 @@ import {
getRequirementsProgress,
getKey,
ResourceType,
selectBestEnrollment,
} from "./helpers"
import {
DashboardCard,
DashboardResource,
DashboardType,
} from "./DashboardCard"
import {
getDistinctLanguageOptions,
getSelectedLanguageOption,
getCourseRunForSelectedLanguage,
getEnrollmentForSelectedLanguage,
getResolvedRunForSelectedLanguage,
} from "./languageOptions"
getDistinctDashboardLanguageOptions,
resolveSlotForLanguage,
} from "./model/dashboardViewModel"
import { coursesQueries } from "api/mitxonline-hooks/courses"
import { programsQueries } from "api/mitxonline-hooks/programs"
import {
Expand Down Expand Up @@ -520,8 +516,12 @@ const ProgramEnrollmentDisplay: React.FC<ProgramEnrollmentDisplayProps> = ({
[programCourses?.results],
)
const languageOptions = React.useMemo(
() => getDistinctLanguageOptions(allProgramCourses),
[allProgramCourses],
() =>
getDistinctDashboardLanguageOptions(
allProgramCourses,
rawEnrollments ?? [],
),
[allProgramCourses, rawEnrollments],
)
const [selectedLanguageKey, setSelectedLanguageKey] = React.useState("")
useEffect(() => {
Expand Down Expand Up @@ -728,56 +728,33 @@ const ProgramEnrollmentDisplay: React.FC<ProgramEnrollmentDisplayProps> = ({
if (item.resourceType === "course") {
const courseEnrollments =
enrollmentsByCourseId[item.course.id] || []
const selectedLanguageOption = getSelectedLanguageOption(
item.course,
selectedLanguageKey,
)
const selectedRun = getCourseRunForSelectedLanguage(
item.course,
selectedLanguageKey,
)
const selectedLanguageEnrollment =
getEnrollmentForSelectedLanguage(
const { displayedEnrollment, displayedRun } =
resolveSlotForLanguage(
item.course,
courseEnrollments,
selectedLanguageOption,
selectedRun,
selectedLanguageKey,
)
const resolvedRun = getResolvedRunForSelectedLanguage(
item.course,
selectedLanguageOption,
selectedRun,
selectedLanguageEnrollment,
)

// When a language is selected, only use an enrollment that
// matches that specific language run. Don't fall back to a
// different-language enrollment, which would show the wrong
// title/URL and a misleading "Continue" CTA.
const hasLanguageSelected = Boolean(
selectedLanguageKey && languageOptions.length > 0,
)
const effectiveEnrollment = hasLanguageSelected
? selectedLanguageEnrollment
: selectBestEnrollment(item.course, courseEnrollments)

const resource = effectiveEnrollment
const resource = displayedEnrollment
? {
type: DashboardType.CourseRunEnrollment,
data: effectiveEnrollment,
data: displayedEnrollment,
}
: { type: DashboardType.Course, data: item.course }

const runId = displayedEnrollment?.run.id ?? displayedRun?.id

return (
<DashboardCardStyled
key={getKey({
resourceType: ResourceType.Course,
id: item.course.id,
runId: effectiveEnrollment?.run.id ?? resolvedRun?.id,
runId,
})}
resource={resource}
programEnrollment={programEnrollment}
showNotComplete={false}
selectedCourseRun={resolvedRun}
selectedCourseRun={displayedRun}
/>
)
}
Expand Down
Loading
Loading