Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
20 changes: 20 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
Release Notes
=============

Version 0.38.2
--------------

- remove unpublished canvas content (#2386)
- add tutorbot problems model and etl (#2373)
- Resolve renovate dependency updates: dotenv v17 and stylelint-config-standard-scss v15 (#2383)
- chore(deps): update dependency jest-extended to v6 (#2378)
- chore(deps): update dependency jest-watch-typeahead to v3 (#2379)
- prevent hero layout shift (#2370)
- update faker (#2371)
- update nextjs (#2372)
- Disallow robot crawlers outside of Prod (#2381)
- fix(deps): update dependency nh3 to ^0.3.0 (#2377)
- fix(deps): update dependency ruff to v0.12.4 (#2376)
- fix(deps): update dependency litellm to v1.74.6 (#2375)
- Improve Dashboard Layout / Fix card Overflows (#2365)
- Filter canvas etl to single course (#2368)
- check that page exists before using it (#2366)
- fix(deps): update django-health-check digest to 7fc1e3a (#2324)

Version 0.38.1 (Released July 22, 2025)
--------------

Expand Down
4 changes: 4 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,10 @@
"POSTHOG_TIMEOUT_MS": {
"description": "Timeout for communication with PostHog API",
"required": false
},
"CANVAS_TUTORBOT_FOLDER": {
"description": "Folder in Canvas course zip files where tutorbot problem and solution files are stored",
"required": false
}
},
"keywords": ["Django", "Python", "MIT", "Office of Digital Learning"],
Expand Down
2 changes: 1 addition & 1 deletion frontends/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"react": "^19"
},
"devDependencies": {
"@faker-js/faker": "^9.0.0",
"@faker-js/faker": "^9.9.0",
"@testing-library/react": "^16.1.0",
"enforce-unique": "^1.3.0",
"jest": "^29.7.0",
Expand Down
2 changes: 1 addition & 1 deletion frontends/api/src/mitxonline/test-utils/factories/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const user: PartialFactory<User> = (overrides = {}): User => {
return mergeOverrides(
{
id: enforcerId.enforce(faker.number.int),
username: faker.internet.userName(),
username: faker.internet.username(),
name: faker.person.fullName(),
email: faker.internet.email(),
legal_address: legalAddress(),
Expand Down
2 changes: 1 addition & 1 deletion frontends/api/src/test-utils/factories/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const user: PartialFactory<User> = (overrides = {}): User => {
last_name: faker.person.lastName(),
is_article_editor: false,
is_learning_path_editor: false,
username: faker.internet.userName(),
username: faker.internet.username(),
is_authenticated: true,
...overrides,
profile: profile(overrides?.profile ?? {}),
Expand Down
6 changes: 3 additions & 3 deletions frontends/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"iso-639-1": "^3.1.4",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"next": "^15.0.2",
"next": "^15.4.1",
"next-nprogress-bar": "^2.4.2",
"ol-components": "0.0.0",
"ol-utilities": "0.0.0",
Expand All @@ -39,7 +39,7 @@
"yup": "^1.4.0"
},
"devDependencies": {
"@faker-js/faker": "^9.0.0",
"@faker-js/faker": "^9.9.0",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2",
Expand All @@ -54,7 +54,7 @@
"eslint-config-next": "^14.2.7",
"http-proxy-middleware": "^3.0.0",
"jest": "^29.7.0",
"jest-extended": "^5.0.0",
"jest-extended": "^6.0.0",
"jest-next-dynamic-ts": "^0.1.1",
"next-router-mock": "^1.0.2",
"ol-test-utilities": "0.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const EnrollmentExpandCollapse: React.FC<EnrollmentExpandCollapseProps> = ({
<EnrollmentsList itemSpacing={"16px"}>
{shownEnrollments.map((course) => (
<DashboardCardStyled
key={course.id}
key={course.key}
Component="li"
dashboardResource={course}
showNotComplete={false}
Expand All @@ -153,7 +153,7 @@ const EnrollmentExpandCollapse: React.FC<EnrollmentExpandCollapseProps> = ({
<HiddenEnrollmentsList itemSpacing={"16px"}>
{hiddenEnrollments.map((course) => (
<DashboardCardStyled
key={course.id}
key={course.key}
Component="li"
dashboardResource={course}
showNotComplete={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const makeGrade = factories.enrollment.grade
const dashboardCourse: PartialFactory<DashboardCourse> = (...overrides) => {
return mergeOverrides<DashboardCourse>(
{
id: faker.string.uuid(),
key: faker.string.uuid(),
coursewareId: faker.string.uuid(),
type: DashboardResourceType.Course,
title: faker.commerce.productName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
const transformed = transform.mitxonlineEnrollments([apiData])
expect(transformed).toHaveLength(1)
expect(transformed[0]).toEqual({
id: `mitxonline-course-${apiData.run.course.id}`,
key: `mitxonline-course-${apiData.run.course.id}-${apiData.run.id}`,
coursewareId: apiData.run.courseware_id ?? null,
type: DashboardResourceType.Course,
title: apiData.run.title,
marketingUrl: apiData.run.course.page.page_url,
marketingUrl: apiData.run.course.page?.page_url,
run: {
startDate: apiData.run.start_date,
endDate: apiData.run.end_date,
Expand Down Expand Up @@ -79,16 +79,20 @@ describe("Transforming mitxonline enrollment data to DashboardResource", () => {
}),
]

const transformedCourses = mitxonlineCourses({
courses: coursesA,
enrollments,
})
const sortedCourses = sortDashboardCourses(
mitxonlineProgram(programA),
mitxonlineCourses({ courses: coursesA, enrollments }),
transformedCourses,
)

expect(sortedCourses.map((course) => course.id)).toEqual([
`mitxonline-course-${coursesA[1].id}`, // Enrolled course
`mitxonline-course-${coursesA[0].id}`, // Completed course
`mitxonline-course-${coursesA[2].id}`, // Not enrolled course
`mitxonline-course-${coursesA[3].id}`, // Not enrolled course
expect(sortedCourses).toEqual([
transformedCourses[1], // Enrolled course
transformedCourses[0], // Completed course
transformedCourses[2], // Not enrolled course
transformedCourses[3], // Not enrolled course
])
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,30 @@ import { groupBy } from "lodash"
const sources = {
mitxonline: "mitxonline",
}
const getId = (
source: string,
resourceType: DashboardResourceType,
id: number,
) => `${source}-${resourceType}-${id}`
type KeyOpts = {
source: string
resourceType: DashboardResourceType
id: number
runId?: number
}
const getKey = ({ source, resourceType, id, runId }: KeyOpts) => {
const base = `${source}-${resourceType}-${id}`
return runId ? `${base}-${runId}` : base
}

const mitxonlineEnrollment = (raw: CourseRunEnrollment): DashboardCourse => {
const course = raw.run.course
return {
id: getId(sources.mitxonline, DashboardResourceType.Course, course.id),
key: getKey({
source: sources.mitxonline,
resourceType: DashboardResourceType.Course,
id: course.id,
runId: raw.run.id,
}),
coursewareId: raw.run.courseware_id ?? null,
type: DashboardResourceType.Course,
title: course.title,
marketingUrl: course.page.page_url,
marketingUrl: course.page?.page_url,
run: {
startDate: raw.run.start_date,
endDate: raw.run.end_date,
Expand All @@ -57,11 +67,16 @@ const mitxonlineUnenrolledCourse = (
): DashboardCourse => {
const run = course.courseruns.find((run) => run.id === course.next_run_id)
return {
id: getId(sources.mitxonline, DashboardResourceType.Course, course.id),
key: getKey({
source: sources.mitxonline,
resourceType: DashboardResourceType.Course,
id: course.id,
runId: run?.id,
}),
coursewareId: run?.courseware_id ?? null,
type: DashboardResourceType.Course,
title: course.title,
marketingUrl: course.page.page_url,
marketingUrl: course.page?.page_url,
run: {
startDate: run?.start_date,
endDate: run?.end_date,
Expand Down Expand Up @@ -100,7 +115,11 @@ const mitxonlineCourses = (raw: {

const mitxonlineProgram = (raw: V2Program): DashboardProgram => {
return {
id: getId(sources.mitxonline, DashboardResourceType.Program, raw.id),
key: getKey({
source: sources.mitxonline,
resourceType: DashboardResourceType.Program,
id: raw.id,
}),
type: DashboardResourceType.Program,
title: raw.title,
programType: raw.program_type,
Expand All @@ -113,7 +132,7 @@ const sortDashboardCourses = (
program: DashboardProgram,
courses: DashboardCourse[],
) => {
return courses.sort((a, b) => {
return [...courses].sort((a, b) => {
const aCompleted = a.enrollment?.status === EnrollmentStatus.Completed
const bCompleted = b.enrollment?.status === EnrollmentStatus.Completed
const aEnrolled = a.enrollment?.status === EnrollmentStatus.Enrolled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const EnrollmentMode = {
type EnrollmentMode = (typeof EnrollmentMode)[keyof typeof EnrollmentMode]

type DashboardCourse = {
id: string
key: string
coursewareId: string | null
title: string
type: typeof DashboardResourceType.Course
Expand Down Expand Up @@ -47,7 +47,7 @@ type DashboardCourseEnrollment = {
}

type DashboardProgram = {
id: string
key: string
type: typeof DashboardResourceType.Program
title: string
programType?: string | null
Expand Down
62 changes: 22 additions & 40 deletions frontends/main/src/app-pages/DashboardPage/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,14 @@ const Background = styled.div(({ theme }) => ({
},
}))

const Page = styled.div(({ theme }) => ({
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
padding: "40px 84px 80px 84px",
gap: "80px",
height: "100%",
[theme.breakpoints.down("md")]: {
padding: "0",
gap: "24px",
},
}))

const DashboardContainer = styled(Container)(({ theme }) => ({
[theme.breakpoints.down("md")]: {
padding: "24px 16px",
gap: "24px",
},
}))
const PageContainer = styled(Container)({
marginTop: "40px",
marginBottom: "80px",
})

const DashboardGrid = styled.div(({ theme }) => ({
display: "grid",
width: "100%",
gridTemplateColumns: "300px minmax(0, 1fr)",
gap: "48px",
[theme.breakpoints.down("md")]: {
Expand All @@ -114,17 +100,15 @@ const DashboardGridItem = styled.div({
},
})

const ProfileSidebar = styled(Card)(({ theme }) => ({
const ProfileSidebar = styled(Card)({
position: "fixed",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
width: "300px",
boxShadow: "-4px 4px 0px 0px #A31F34",
[theme.breakpoints.down("md")]: {
position: "relative",
},
}))
transform: "translateX(4px)", // keep solid shadow from bleeding into page margins
})

const ProfilePhotoContainer = styled.div(({ theme }) => ({
display: "flex",
Expand Down Expand Up @@ -382,22 +366,20 @@ const DashboardPage: React.FC<{

return (
<Background>
<Page>
<LearningResourceDrawer />
<DashboardContainer>
<DashboardGrid>
<TabContext value={tabValue}>
<DashboardGridItem>
<MobileOnly>{mobileTabs}</MobileOnly>
<DesktopOnly>{desktopTabs}</DesktopOnly>
</DashboardGridItem>
<DashboardGridItem>
<TabPanelStyled value={tabValue}>{children}</TabPanelStyled>
</DashboardGridItem>
</TabContext>
</DashboardGrid>
</DashboardContainer>
</Page>
<PageContainer>
<DashboardGrid>
<TabContext value={tabValue}>
<DashboardGridItem>
<MobileOnly>{mobileTabs}</MobileOnly>
<DesktopOnly>{desktopTabs}</DesktopOnly>
</DashboardGridItem>
<DashboardGridItem>
<TabPanelStyled value={tabValue}>{children}</TabPanelStyled>
</DashboardGridItem>
</TabContext>
</DashboardGrid>
</PageContainer>
<LearningResourceDrawer />
</Background>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const OrgProgramDisplay: React.FC<{
{transform.sortDashboardCourses(program, courses).map((course) => (
<DashboardCardStyled
Component="li"
key={course.id}
key={course.key}
dashboardResource={course}
courseNoun="Module"
offerUpgrade={false}
Expand Down Expand Up @@ -178,7 +178,7 @@ const OrganizationContentInternal: React.FC<
const programLoading = courseGroups[index].isLoading
return (
<OrgProgramDisplay
key={program.id}
key={program.key}
program={program}
courses={courses}
coursesLoading={courseGroups[index].isLoading}
Expand Down
12 changes: 12 additions & 0 deletions frontends/main/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ export async function generateMetadata({
})
}

/**
* Fallback to dynamic rendering to load as much of the page as possible.
*
* We could alternatively wrap the carousels (which use useSearchParams) in a
* suspense boundary. This ostensibly leads to a faster response, since the
* server can render the rest of the homepage at build time.
*
* But... We ache the result on CDN anyway, so it's essentially pre-build for
* most requests, anyway.
*/
export const dynamic = "force-dynamic"

const Page: React.FC = async () => {
const { dehydratedState } = await prefetch([
// Featured Courses carousel "All"
Expand Down
Loading
Loading