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
10 changes: 3 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,11 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
libcairo2-dev \
poppler-utils \
default-jre && \
# chromium 150.0.7871.46 crashes on startup in headless mode (Debian bug
# #1141488), breaking all chromedriver sessions. Pin the last working
# build from snapshot.debian.org; unpin once a fixed version ships.
echo "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian-security/20260628T000000Z/ trixie-security main" >> /etc/apt/sources.list && \
echo "deb http://deb.debian.org/debian/ sid main" >> /etc/apt/sources.list && \
apt-get update -qqy && \
apt-get install -qqy --no-install-recommends \
chromium=149.0.7827.196-1~deb13u1 \
chromium-common=149.0.7827.196-1~deb13u1 \
chromium-driver=149.0.7827.196-1~deb13u1
-o Dpkg::Options::="--force-confnew" \
chromium chromium-driver

FROM base AS deps

Expand Down
15 changes: 15 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
Release Notes
=============

Version 0.78.5
--------------

- fix(docker): force non-interactive conffile resolution for the sid chromium install (#3888)
- Trust X-Forwarded-Proto from the API gateway (#3876)
- fix: Make unenrolled module titles keyboard-focusable (#3871)
- Take the canonical page URL from learn_url (#3863)
- Bound Renovate to the service versions we actually run (#3865)
- Add refunds to the receipt page (#3823)
- vector search: add course metadata to context (#3852)
- Revert "Pin chromium to 149.0.7827.196 to avoid broken 150 headless build (#3584)" (#3878)
- Move the shared media players into page-components (#3875)
- vector search: embedding call request hedging (#3853)
- Update dependency youtube-transcript-api to >=1.2.4,<1.3 (#3861)

Version 0.78.2 (Released September 01, 2026)
--------------

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ services:
start_period: 30s

qdrant:
image: qdrant/qdrant:latest
image: qdrant/qdrant:v1.18.3
ports:
- "6333:6333"
volumes:
Expand Down
2 changes: 1 addition & 1 deletion frontends/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"dependencies": {
"@mitodl/mit-learn-api-axios": "2026.8.17",
"@mitodl/mitxonline-api-axios": "2026.8.31",
"@mitodl/mitxonline-api-axios": "2026.8.31-1",
"@tanstack/react-query": "^5.66.0",
"axios": "^1.12.2",
"tiny-invariant": "^1.3.3"
Expand Down
26 changes: 25 additions & 1 deletion frontends/api/src/mitxonline/hooks/orders/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
import { orderQueries, orderKeys } from "./queries"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { ordersApi } from "../../clients"
import type { RefundRequestRequest } from "@mitodl/mitxonline-api-axios/v2"
import type { MutationHookOptions } from "../../../mutations/mutationMeta"

export { orderQueries, orderKeys }
/**
* Submit a learner's refund request for an order.
*
* Invalidates the order's receipt, since a successful request moves its
* `refund_status` to `requested` and the card rendering it has to follow.
*/
const useCreateRefundRequest = ({ meta }: MutationHookOptions = {}) => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (opts: RefundRequestRequest) =>
ordersApi.ordersRefundRequestsCreate({ RefundRequestRequest: opts }),
onSettled: (_data, _error, opts) => {
queryClient.invalidateQueries({
queryKey: orderKeys.receipt(opts.order),
})
},
meta,
})
}

export { orderQueries, orderKeys, useCreateRefundRequest }
25 changes: 23 additions & 2 deletions frontends/api/src/mitxonline/test-utils/factories/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import type {
PaginatedOrderHistoryList,
Product,
RedeemedDiscount,
RefundRequest,
TransactionLine,
} from "@mitodl/mitxonline-api-axios/v2"
import { RefundStatusEnum } from "@mitodl/mitxonline-api-axios/v2"

const transactionLine = (
overrides: Partial<TransactionLine> = {},
Expand All @@ -26,6 +28,8 @@ const transactionLine = (
total_paid: faker.commerce.price({ min: 50, max: 500 }),
discount: "0.00",
price: faker.commerce.price({ min: 50, max: 500 }),
// MITx Online course runs almost always carry an audit track.
has_free_audit: true,
...overrides,
})

Expand Down Expand Up @@ -85,14 +89,30 @@ const order = (overrides: Partial<Order> = {}): Order => ({
lines: [transactionLine()],
discounts: [],
refunds: [],
refund_eligible: false,
// Default to the state a freshly purchased order is in, so tests opt into
// the other refund states rather than out of an arbitrary one.
refund_eligible: true,
refund_status: RefundStatusEnum.Eligible,
refund_deadline: faker.date.future().toISOString(),
refund_requested_on: null,
refund_reviewed_on: null,
reference_number: faker.string.alphanumeric(10),
created_on: faker.date.past().toISOString(),
transactions: orderTransactions(),
street_address: orderStreetAddress(),
...overrides,
})

const refundRequest = (
overrides: Partial<RefundRequest> = {},
): RefundRequest => ({
order: faker.number.int(),
refund_reason: "course_not_as_expected",
refund_reason_text: "",
consent_given: true,
...overrides,
})

/**
* The default `purchasable_object` has only an `id`, which matches no variant —
* pass a shaped object (with `course`, or neither `course` nor `run_tag`) when the
Expand Down Expand Up @@ -135,7 +155,7 @@ const orderHistory = (overrides: Partial<OrderHistory> = {}): OrderHistory => ({
created_on: faker.date.past().toISOString(),
titles: [],
updated_on: faker.date.past().toISOString(),
refund_eligible: false,
refund_eligible: true,
...overrides,
})

Expand All @@ -159,5 +179,6 @@ export {
line,
product,
redeemedDiscount,
refundRequest,
transactionLine,
}
1 change: 1 addition & 0 deletions frontends/api/src/mitxonline/test-utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ const orders = {
`${getApiBaseUrl()}/api/v0/orders/receipt/${orderId}/`,
historyList: (params?: OrdersApiOrdersHistoryListRequest) =>
`${getApiBaseUrl()}/api/v0/orders/history/${queryify(params)}`,
refundRequests: () => `${getApiBaseUrl()}/api/v0/orders/refund-requests/`,
}

const verifiedProgramEnrollments = {
Expand Down
4 changes: 2 additions & 2 deletions frontends/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"@mitodl/arithmix": "^0.2.5",
"@mitodl/course-search-utils": "^3.8.0",
"@mitodl/hacksnack": "^0.1.2",
"@mitodl/mitxonline-api-axios": "2026.8.31",
"@mitodl/smoot-design": "6.33.4",
"@mitodl/mitxonline-api-axios": "2026.8.31-1",
"@mitodl/smoot-design": "6.34.0",
"@mui/base": "5.0.0-beta.70",
"@mui/material": "^6.4.5",
"@mui/material-nextjs": "^6.4.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,37 @@ const TitleLink = styled(Link)(({ theme }) => ({
...theme.typography.subtitle1,
}))

const TitleText = styled.h3<{ clickable?: boolean }>(
({ theme, clickable }) => ({
margin: 0,
...theme.typography.subtitle1,
color: theme.custom.colors.darkGray2,
cursor: clickable ? "pointer" : "default",
[theme.breakpoints.down("sm")]: {
maxWidth: "calc(100% - 16px)",
},
}),
)
const TitleText = styled.h3(({ theme }) => ({
margin: 0,
...theme.typography.subtitle1,
color: theme.custom.colors.darkGray2,
[theme.breakpoints.down("sm")]: {
maxWidth: "calc(100% - 16px)",
},
}))

/**
* A title rendered as an actual button (nested inside a TitleHeading), for
* cards whose title click triggers an action rather than navigation. Keeps
* the heading itself focusable-by-heading-role for AT heading navigation
* while making the title text a real, keyboard-operable control.
*/
const TitleButton = styled.button(({ theme }) => ({
margin: 0,
padding: 0,
border: "none",
background: "none",
appearance: "none",
display: "block",
width: "100%",
textAlign: "left",
cursor: "pointer",
color: theme.custom.colors.darkGray2,
...theme.typography.subtitle1,
[theme.breakpoints.down("sm")]: {
maxWidth: "calc(100% - 16px)",
},
}))

const SubtitleLinkRoot = styled.div(({ theme }) => ({
display: "flex",
Expand Down Expand Up @@ -336,6 +356,7 @@ export {
TitleHeading,
TitleLink,
TitleText,
TitleButton,
SubtitleLinkRoot,
SubtitleLink,
MenuButton,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,16 +270,14 @@ export const EnrolledCourseCard = ({
const mitxOnlineUser = useQuery(mitxUserQueries.me())
const isStaff = mitxOnlineUser.data?.is_staff
/**
* Only verified enrollments can have a receipt, so the lookup is skipped
* entirely for audit ones. Every card shares one `orders/history` query (same
* cache key), so this is a single request for the whole dashboard rather than
* one per card.
* Enrollment mode does not decide this: a refund returns the learner to audit,
* and the receipt is where they confirm it went through. Whether an order
* exists is the only question, and the lookup answers it.
*
* Every card shares one `orders/history` query (same cache key), so this is a
* single request for the whole dashboard rather than one per card.
*/
const receiptResolution = useOrderIdForRun(
isVerifiedEnrollmentMode(enrollment?.enrollment_mode)
? (run?.id ?? null)
: null,
)
const receiptResolution = useOrderIdForRun(run?.id ?? null)
const title = isCompact ? course.title : run?.title || course.title
const coursewareUrl = run?.courseware_url
const certificateLink = getCertificateLink(
Expand Down Expand Up @@ -484,7 +482,6 @@ export const EnrolledCourseCard = ({
)

const receiptMenuItem = getReceiptMenuItem(
enrollment?.enrollment_mode,
receiptResolution,
receiptByRunView(run.id),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "@/test-utils"
import { makeRequest } from "api/test-utils"
import * as mitxonline from "api/mitxonline-test-utils"
import { setupOrderHistory } from "./test-utils"
import { ProgramAsCourseCard } from "./ProgramAsCourseCard"
import { waitFor } from "@testing-library/react"
import invariant from "tiny-invariant"
Expand Down Expand Up @@ -91,6 +92,9 @@ describe("ProgramAsCourseCard", () => {
mitxonline.urls.userMe.get(),
mitxonline.factories.user.user(),
)
// Every enrollment card looks up order history to decide whether to offer a
// Receipt item, regardless of enrollment mode.
setupOrderHistory()

return {
courseProgram: program,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,11 @@ export const ProgramEnrollmentCard = ({
programEnrollment.enrollment_mode,
)
/**
* Skipped for audit enrollments, which never have a receipt. Shares one
* `orders/history` query with every other card on the dashboard.
* Not gated on enrollment mode: a refund returns the learner to audit, and the
* receipt is where they confirm it went through. Shares one `orders/history`
* query with every other card on the dashboard.
*/
const receiptResolution = useOrderIdForProgram(
upgradedAndIncomplete ? programId : null,
)
const receiptResolution = useOrderIdForProgram(programId)
const displayMode = program.display_mode
const titleSection = (
<Stack gap="12px">
Expand Down Expand Up @@ -125,7 +124,6 @@ export const ProgramEnrollmentCard = ({
})
}
const receiptMenuItem = getReceiptMenuItem(
programEnrollment.enrollment_mode,
receiptResolution,
receiptByProgramView(programId),
)
Expand Down
Loading
Loading