Skip to content

Commit 934fec8

Browse files
Ahtesham QuraishAhtesham Quraish
authored andcommitted
rebase with main
2 parents 2ffae08 + 088a9c3 commit 934fec8

37 files changed

Lines changed: 1871 additions & 627 deletions

File tree

RELEASE.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
11
Release Notes
22
=============
33

4+
Version 0.55.5 (Released February 25, 2026)
5+
--------------
6+
7+
- program dashboard course enrollment (#2961)
8+
9+
Version 0.55.4 (Released February 24, 2026)
10+
--------------
11+
12+
- fix: fix the learning resource card space issue (#2979)
13+
14+
Version 0.55.3 (Released February 24, 2026)
15+
--------------
16+
17+
- fix: change the new article route (#2977)
18+
- fix: remove the articles route and its relevant code (#2974)
19+
20+
Version 0.55.1 (Released February 24, 2026)
21+
--------------
22+
23+
- fix-duplicate-property-error (#2970)
24+
- fix: improvements article listing page and home page (#2963)
25+
- Product Page Instructor Section (#2964)
26+
- fix(deps): update dependency litellm to v1.81.13 (#2741)
27+
- fix(deps): update dependency openai to v2 (#2732)
28+
- fix(deps): update dependency llama-index-llms-openai to ^0.6.0 (#2737)
29+
- [pre-commit.ci] pre-commit autoupdate (#2895)
30+
- fix(deps): update dependency llama-index to ^0.14.0 (#2736)
31+
- Product page style updates (#2962)
32+
- add CLAUDE.md pointing to AGENTS.md (#2966)
33+
- fix(deps): update dependency cryptography to v46 [security] (#2946)
34+
- fix(deps): update dependency django-imagekit to v6 (#2762)
35+
- fix(deps): update dependency cffi to v2 (#2759)
36+
- fix: improvements in editor exprience (#2954)
37+
- Replace uwsgi with granian, remove heroku-specific files (#2956)
38+
439
Version 0.54.1 (Released February 19, 2026)
540
--------------
641

frontends/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"ol-test-utilities": "0.0.0"
3030
},
3131
"dependencies": {
32-
"@mitodl/mitxonline-api-axios": "^2026.2.5",
32+
"@mitodl/mitxonline-api-axios": "^2026.2.19",
3333
"@tanstack/react-query": "^5.66.0",
3434
"axios": "^1.12.2",
3535
"tiny-invariant": "^1.3.3"

frontends/api/src/mitxonline/hooks/enrollment/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ const useCreateEnrollment = () => {
3232
queryClient.invalidateQueries({
3333
queryKey: enrollmentKeys.courseRunEnrollmentsList(),
3434
})
35+
queryClient.invalidateQueries({
36+
queryKey: enrollmentKeys.programEnrollmentsList(),
37+
})
3538
},
3639
})
3740
}

frontends/api/src/mitxonline/test-utils/factories/enrollment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ const programEnrollmentV3: PartialFactory<V3UserProgramEnrollment> = (
206206
link: `/certificate/program/${faker.string.uuid()}/`,
207207
}
208208
: null,
209+
enrollment_mode: faker.helpers.arrayElement(["audit", "verified", null]),
209210
program: program,
210211
}
211212
return mergeOverrides<V3UserProgramEnrollment>(defaults, overrides)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * as factories from "./factories"
2+
export { RequirementTreeBuilder } from "./factories/requirements"
23
export * as urls from "./urls"

frontends/main/next.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ const nextConfig = {
4747
destination: "/enrollmentcode/:code",
4848
permanent: true,
4949
},
50+
{
51+
// can be removed once fastly redirect is in place
52+
source: "/articles/:slug*",
53+
destination: "/news/:slug*",
54+
permanent: true,
55+
},
56+
{
57+
// can be removed once fastly redirect is in place
58+
source: "/articles",
59+
destination: "/news",
60+
permanent: true,
61+
},
5062
]
5163
},
5264

frontends/main/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@emotion/styled": "^11.11.0",
1515
"@floating-ui/react": "^0.27.16",
1616
"@mitodl/course-search-utils": "^3.5.0",
17-
"@mitodl/mitxonline-api-axios": "^2026.2.5",
17+
"@mitodl/mitxonline-api-axios": "^2026.2.19",
1818
"@mitodl/smoot-design": "^6.24.0",
1919
"@mui/material": "^6.4.5",
2020
"@mui/material-nextjs": "^6.4.3",

frontends/main/src/app-pages/Articles/ArticleDraftListingPage.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ import {
1212
LoadingSpinner,
1313
Typography,
1414
} from "ol-components"
15-
import { Permission, useUserHasPermission } from "api/hooks/user"
15+
import { Permission } from "api/hooks/user"
1616
import { useArticleList } from "api/hooks/articles"
1717
import type { RichTextArticle } from "api/v1"
1818
import { LocalDate } from "ol-utilities"
1919
import { RiArrowLeftLine, RiArrowRightLine } from "@remixicon/react"
2020
import { ArticleBanner, DEFAULT_BACKGROUND_IMAGE_URL } from "./ArticleBanner"
2121
import { extractFirstImageFromArticle } from "@/common/articleUtils"
22-
import { notFound } from "next/navigation"
2322
import { articlesDraftView, articlesView } from "@/common/urls"
23+
import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute"
2424

2525
const PAGE_SIZE = 20
2626

@@ -109,8 +109,6 @@ const ArticleDraftPage: React.FC = () => {
109109
draft: true, // Filter for drafts only on the backend
110110
})
111111

112-
const isArticleEditor = useUserHasPermission(Permission.ArticleEditor)
113-
114112
useEffect(() => {
115113
if (page > 1 && scrollRef.current) {
116114
scrollRef.current.scrollIntoView({ behavior: "smooth", block: "start" })
@@ -120,11 +118,11 @@ const ArticleDraftPage: React.FC = () => {
120118
const draftArticles = articles?.results
121119
const totalPages = articles?.count ? Math.ceil(articles.count / PAGE_SIZE) : 0
122120

123-
if (!isLoadingArticles && !isArticleEditor) {
124-
return notFound()
121+
if (isLoadingArticles) {
122+
return <LoadingSpinner loading={isLoadingArticles} />
125123
}
126124
return (
127-
<>
125+
<RestrictedRoute requires={Permission.ArticleEditor}>
128126
<ArticleBanner
129127
title="Draft Articles"
130128
description="Manage your unpublished articles that are currently in draft status."
@@ -179,7 +177,7 @@ const ArticleDraftPage: React.FC = () => {
179177
)}
180178
</Container>
181179
</PageWrapper>
182-
</>
180+
</RestrictedRoute>
183181
)
184182
}
185183

frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,179 @@ describe.each([
11611161
},
11621162
)
11631163

1164+
describe("B2C (non-B2B) Enrollment", () => {
1165+
test.each(ENROLLMENT_TRIGGERS)(
1166+
"Clicking $trigger on non-B2B course opens CourseEnrollmentDialog",
1167+
async ({ trigger }) => {
1168+
const userData = mitxUser()
1169+
setMockResponse.get(mitxonline.urls.userMe.get(), userData)
1170+
1171+
const run = mitxonline.factories.courses.courseRun({
1172+
b2b_contract: null, // Non-B2B course
1173+
is_enrollable: true,
1174+
})
1175+
const course = dashboardCourse({
1176+
courseruns: [run],
1177+
next_run_id: run.id,
1178+
})
1179+
1180+
renderWithProviders(
1181+
<DashboardCard
1182+
resource={{ type: DashboardType.Course, data: course }}
1183+
/>,
1184+
)
1185+
1186+
const card = getCard()
1187+
const triggerElement =
1188+
trigger === "button"
1189+
? within(card).getByTestId("courseware-button")
1190+
: within(card).getByText(course.title)
1191+
1192+
await user.click(triggerElement)
1193+
1194+
// Should open the CourseEnrollmentDialog, not JustInTimeDialog
1195+
await screen.findByRole("dialog", { name: course.title })
1196+
expect(
1197+
screen.queryByRole("dialog", { name: "Just a Few More Details" }),
1198+
).not.toBeInTheDocument()
1199+
},
1200+
)
1201+
})
1202+
1203+
describe("Verified Program Enrollment", () => {
1204+
test.each(ENROLLMENT_TRIGGERS)(
1205+
"Clicking $trigger on course in verified program does one-click enrollment",
1206+
async ({ trigger }) => {
1207+
const userData = mitxUser()
1208+
setMockResponse.get(mitxonline.urls.userMe.get(), userData)
1209+
1210+
const run = mitxonline.factories.courses.courseRun({
1211+
b2b_contract: null,
1212+
is_enrollable: true,
1213+
courseware_url: faker.internet.url(),
1214+
})
1215+
const course = dashboardCourse({
1216+
courseruns: [run],
1217+
next_run_id: run.id,
1218+
})
1219+
1220+
const programEnrollment =
1221+
mitxonline.factories.enrollment.programEnrollmentV3({
1222+
enrollment_mode: "verified",
1223+
})
1224+
1225+
// Mock the enrollment endpoint
1226+
setMockResponse.post(mitxonline.urls.enrollment.enrollmentsListV1(), {})
1227+
1228+
renderWithProviders(
1229+
<DashboardCard
1230+
resource={{ type: DashboardType.Course, data: course }}
1231+
programEnrollment={programEnrollment}
1232+
/>,
1233+
)
1234+
1235+
const card = getCard()
1236+
const triggerElement =
1237+
trigger === "button"
1238+
? within(card).getByTestId("courseware-button")
1239+
: within(card).getByText(course.title)
1240+
1241+
await user.click(triggerElement)
1242+
1243+
// Should call enrollment endpoint
1244+
await waitFor(() => {
1245+
expect(mockAxiosInstance.request).toHaveBeenCalledWith(
1246+
expect.objectContaining({
1247+
method: "POST",
1248+
url: mitxonline.urls.enrollment.enrollmentsListV1(),
1249+
}),
1250+
)
1251+
})
1252+
1253+
expect(
1254+
screen.queryByRole("dialog", { name: course.title }),
1255+
).not.toBeInTheDocument()
1256+
expect(
1257+
screen.queryByRole("dialog", { name: "Just a Few More Details" }),
1258+
).not.toBeInTheDocument()
1259+
},
1260+
)
1261+
1262+
test("Audit program enrollment opens CourseEnrollmentDialog", async () => {
1263+
const userData = mitxUser()
1264+
setMockResponse.get(mitxonline.urls.userMe.get(), userData)
1265+
1266+
const run = mitxonline.factories.courses.courseRun({
1267+
b2b_contract: null,
1268+
is_enrollable: true,
1269+
})
1270+
const course = dashboardCourse({
1271+
courseruns: [run],
1272+
next_run_id: run.id,
1273+
})
1274+
1275+
const programEnrollment =
1276+
mitxonline.factories.enrollment.programEnrollmentV3({
1277+
enrollment_mode: "audit", // Audit, not verified
1278+
})
1279+
1280+
renderWithProviders(
1281+
<DashboardCard
1282+
resource={{ type: DashboardType.Course, data: course }}
1283+
programEnrollment={programEnrollment}
1284+
/>,
1285+
)
1286+
1287+
const card = getCard()
1288+
const button = within(card).getByTestId("courseware-button")
1289+
1290+
await user.click(button)
1291+
1292+
// Should open the CourseEnrollmentDialog for audit enrollments
1293+
await screen.findByRole("dialog", { name: course.title })
1294+
})
1295+
})
1296+
1297+
describe("CourseEnrollmentDialog", () => {
1298+
test("shows course runs as options in dialog", async () => {
1299+
const userData = mitxUser()
1300+
setMockResponse.get(mitxonline.urls.userMe.get(), userData)
1301+
1302+
const run1 = mitxonline.factories.courses.courseRun({
1303+
b2b_contract: null,
1304+
is_enrollable: true,
1305+
title: "Fall 2025",
1306+
})
1307+
const run2 = mitxonline.factories.courses.courseRun({
1308+
b2b_contract: null,
1309+
is_enrollable: true,
1310+
title: "Spring 2026",
1311+
})
1312+
const course = dashboardCourse({
1313+
title: "Test Course Title",
1314+
courseruns: [run1, run2],
1315+
next_run_id: run1.id,
1316+
})
1317+
1318+
renderWithProviders(
1319+
<DashboardCard
1320+
resource={{ type: DashboardType.Course, data: course }}
1321+
/>,
1322+
)
1323+
1324+
const card = getCard()
1325+
const startButton = within(card).getByTestId("courseware-button")
1326+
await user.click(startButton)
1327+
1328+
const dialog = await screen.findByRole("dialog", {
1329+
name: "Test Course Title",
1330+
})
1331+
1332+
// Dialog should show course runs as options
1333+
expect(within(dialog).getByText("Choose a date:")).toBeInTheDocument()
1334+
})
1335+
})
1336+
11641337
describe("Stacked Variant", () => {
11651338
test("applies stacked variant styling", () => {
11661339
setupUserApis()

0 commit comments

Comments
 (0)