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

Version 0.71.14
---------------

- Retry embeddings tasks on Qdrant gRPC DEADLINE_EXCEEDED (#3520)
- chore: Remove product page feature flag (#3501)
- Ingest contentfile archives for all runs (two-tier indexing) (#3503)
- Program certificate title from CMS "Certificate Title" (behind flag) (#3512)
- remove semantic chunker and dependance on langchain-experimental (#3514)

Version 0.71.12 (Released June 23, 2026)
---------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ import CertificatePage from "./CertificatePage"
import { CertificateType } from "@/common/certificateUtils"
import SharePopover from "@/components/SharePopover/SharePopover"
import * as mitxonline from "api/mitxonline-test-utils"
import { useFeatureFlagEnabled } from "posthog-js/react"
import {
FACEBOOK_SHARE_BASE_URL,
TWITTER_SHARE_BASE_URL,
LINKEDIN_SHARE_BASE_URL,
} from "@/common/urls"

jest.mock("posthog-js/react", () => ({
...jest.requireActual("posthog-js/react"),
useFeatureFlagEnabled: jest.fn(),
}))
const mockedUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled)

describe("CertificatePage", () => {
beforeEach(() => {
// Default the CMS-title flag ON; individual tests override as needed.
mockedUseFeatureFlagEnabled.mockReturnValue(true)
const mitxUser = mitxonline.factories.user.user()
setMockResponse.get(mitxonline.urls.userMe.get(), mitxUser)
})
Expand Down Expand Up @@ -84,9 +93,11 @@ describe("CertificatePage", () => {
await screen.findAllByText(certificate.uuid)
})

it("renders a program certificate", async () => {
it("renders a program certificate with the CMS product name as the title", async () => {
const certificate = factories.mitxonline.programCertificate()
certificate.program.program_type = "Program"
certificate.certificate_page.product_name =
"Custom Program Certificate Title"
setMockResponse.get(
mitxonline.urls.certificates.programCertificatesRetrieve({
uuid: certificate.uuid,
Expand All @@ -101,7 +112,7 @@ describe("CertificatePage", () => {
/>,
)

await screen.findAllByText(certificate.program.title)
await screen.findAllByText("Custom Program Certificate Title")
const badge = await screen.findByTestId("certificate-badge-label")
expect(within(badge).getByText("Program")).toBeInTheDocument()
expect(within(badge).getByText("Certificate")).toBeInTheDocument()
Expand All @@ -117,6 +128,50 @@ describe("CertificatePage", () => {
await screen.findAllByText(certificate.uuid)
})

it("falls back to the program title when the certificate page has no product name", async () => {
const certificate = factories.mitxonline.programCertificate()
certificate.program.program_type = "Program"
certificate.certificate_page.product_name = ""
setMockResponse.get(
mitxonline.urls.certificates.programCertificatesRetrieve({
uuid: certificate.uuid,
}),
certificate,
)
renderWithProviders(
<CertificatePage
certificateType={CertificateType.Program}
uuid={certificate.uuid}
pageUrl={`https://${process.env.NEXT_PUBLIC_ORIGIN}/certificate/program/${certificate.uuid}`}
/>,
)

await screen.findAllByText(certificate.program.title)
})

it("shows the program title (not the CMS title) when the flag is off", async () => {
mockedUseFeatureFlagEnabled.mockReturnValue(false)
const certificate = factories.mitxonline.programCertificate()
certificate.program.program_type = "Program"
certificate.certificate_page.product_name = "Custom CMS Title"
setMockResponse.get(
mitxonline.urls.certificates.programCertificatesRetrieve({
uuid: certificate.uuid,
}),
certificate,
)
renderWithProviders(
<CertificatePage
certificateType={CertificateType.Program}
uuid={certificate.uuid}
pageUrl={`https://${process.env.NEXT_PUBLIC_ORIGIN}/certificate/program/${certificate.uuid}`}
/>,
)

await screen.findAllByText(certificate.program.title)
expect(screen.queryByText("Custom CMS Title")).not.toBeInTheDocument()
})

it("renders a MicroMasters program certificate badge with the registered mark", async () => {
const certificate = factories.mitxonline.programCertificate()
certificate.program.program_type = "MicroMasters®"
Expand Down Expand Up @@ -166,7 +221,7 @@ describe("CertificatePage", () => {
/>,
)

await screen.findAllByText(certificate.program.title)
await screen.findAllByText(certificate.user.name!)

expect(
screen.queryByRole("button", { name: "Download PDF" }),
Expand Down Expand Up @@ -204,7 +259,7 @@ describe("CertificatePage", () => {
/>,
)

await screen.findAllByText(certificate.program.title)
await screen.findAllByText(certificate.user.name!)

await screen.findByRole("button", { name: "Download PDF" })
await screen.findByRole("button", { name: "Share" })
Expand Down
43 changes: 32 additions & 11 deletions frontends/main/src/app-pages/CertificatePage/CertificatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import SharePopover from "@/components/SharePopover/SharePopover"
import { DigitalCredentialDialog } from "./DigitalCredentialDialog"
import {
getCertificateInfo,
getCertificateTitle,
getCertificateBadgeLines,
getCertificateBadgeTypography,
getVerifiableCredentialLinkedInURL,
getCertificateLinkedInUrl,
getVerifiableCredentialDownloadAPIURL,
CertificateType,
} from "@/common/certificateUtils"
import { useFeatureFlagEnabled } from "posthog-js/react"
import { FeatureFlags } from "@/common/feature_flags"

const Page = styled.div(({ theme }) => ({
backgroundImage: `url(${backgroundImage.src})`,
Expand Down Expand Up @@ -626,11 +629,11 @@ const Certificate = ({

const CourseCertificate = ({
certificate,
title,
}: {
certificate: V2CourseRunCertificate
title: string
}) => {
const title = certificate.course_run.course.title

const userName = certificate.user.name

const signatories = certificate.certificate_page.signatory_items
Expand All @@ -649,11 +652,11 @@ const CourseCertificate = ({

const ProgramCertificate = ({
certificate,
title,
}: {
certificate: V2ProgramCertificate
title: string
}) => {
const title = certificate.program.title

const userName = certificate.user.name

const ceus = certificate.certificate_page.CEUs
Expand Down Expand Up @@ -685,6 +688,11 @@ const CertificatePage: React.FC<{

const { data: userData } = useQuery(mitxUserQueries.me())

// Gates whether the certificate title comes from the CMS "Certificate Title"
// (product_name) field or the program/course title. Defaults to the
// program/course title until the flag loads / is enabled.
const useCmsTitle = !!useFeatureFlagEnabled(FeatureFlags.CmsCertificateTitle)

const {
data: courseCertificateData,
isLoading: isCourseLoading,
Expand Down Expand Up @@ -750,6 +758,18 @@ const CertificatePage: React.FC<{
return notFound()
}

let title = ""
if (certificateType === CertificateType.Course) {
title = courseCertificateData?.course_run.course.title ?? ""
} else if (useCmsTitle) {
title = getCertificateTitle(
programCertificateData?.certificate_page?.product_name,
programCertificateData?.program.title ?? "",
)
} else {
title = programCertificateData?.program.title ?? ""
}

const download = async () => {
const res = await fetch(`/certificate/${certificateType}/${uuid}/pdf`)
const blob = await res.blob()
Expand All @@ -763,11 +783,6 @@ const CertificatePage: React.FC<{
window.URL.revokeObjectURL(url)
}

const title =
certificateType === CertificateType.Course
? courseCertificateData?.course_run.course.title
: programCertificateData?.program.title

const { displayType } = getCertificateInfo(
programCertificateData?.program?.program_type,
)
Expand Down Expand Up @@ -849,9 +864,15 @@ const CertificatePage: React.FC<{
) : null}
<PrintContainer ref={contentRef}>
{certificateType === CertificateType.Course ? (
<CourseCertificate certificate={courseCertificateData!} />
<CourseCertificate
certificate={courseCertificateData!}
title={title}
/>
) : (
<ProgramCertificate certificate={programCertificateData!} />
<ProgramCertificate
certificate={programCertificateData!}
title={title}
/>
)}
</PrintContainer>
<Note>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ import { waitFor } from "@testing-library/react"
import invariant from "tiny-invariant"
import moment from "moment"
import NiceModal from "@ebay/nice-modal-react"
import { useFeatureFlagEnabled } from "posthog-js/react"
import { UnenrollProgramDialog } from "./DashboardDialogs"
import { mitxonlineLegacyUrl } from "@/common/mitxonline"

jest.mock("posthog-js/react")
const mockedUseFeatureFlagEnabled = jest
.mocked(useFeatureFlagEnabled)
.mockImplementation(() => false)

describe("ProgramAsCourseCard", () => {
setupLocationMock()
Expand Down Expand Up @@ -431,47 +427,7 @@ describe("ProgramAsCourseCard", () => {
expect(certButton).not.toBeInTheDocument()
})

test("shows legacy details link in context menu when product pages flag is disabled", async () => {
mockedUseFeatureFlagEnabled.mockReturnValue(false)
const cardData = setupCardData({ includeProgramEnrollment: true })

renderWithProviders(
<ProgramAsCourseCard
courseProgram={cardData.courseProgram}
moduleCourses={cardData.moduleCourses}
moduleEnrollmentsByCourseId={cardData.moduleEnrollmentsByCourseId}
courseProgramEnrollment={cardData.courseProgramEnrollment}
/>,
)

await screen.findByText(cardData.courseProgram.title)
const programCard = screen.getByTestId("program-as-course-card")
await user.click(within(programCard).getAllByLabelText("More options")[0])

const detailsLink = await screen.findByRole("menuitem", {
name: "View Course Details",
})
expect(detailsLink).toHaveAttribute(
"href",
expect.stringContaining(
`/programs/${cardData.courseProgram.readable_id}`,
),
)
expect(detailsLink).toHaveAttribute(
"href",
expect.stringContaining("ecom-service=true"),
)

expect(
screen.getByRole("menuitem", { name: "Program Record" }),
).toHaveAttribute(
"href",
mitxonlineLegacyUrl(`/records/${cardData.courseProgram.id}/`),
)
})

test("shows product-page details link in context menu when product pages flag is enabled", async () => {
mockedUseFeatureFlagEnabled.mockReturnValue(true)
test("shows product-page details link in context menu", async () => {
const cardData = setupCardData({ includeProgramEnrollment: true })

renderWithProviders(
Expand Down Expand Up @@ -504,7 +460,6 @@ describe("ProgramAsCourseCard", () => {
})

test("clicking Unenroll menu item opens UnenrollProgramDialog with readable_id", async () => {
mockedUseFeatureFlagEnabled.mockReturnValue(false)
const cardData = setupCardData({ includeProgramEnrollment: true })
invariant(cardData.courseProgramEnrollment)
cardData.courseProgramEnrollment.enrollment_mode = "audit"
Expand Down Expand Up @@ -532,7 +487,6 @@ describe("ProgramAsCourseCard", () => {
})

test("does not show Unenroll option in context menu for verified enrollment", async () => {
mockedUseFeatureFlagEnabled.mockReturnValue(false)
const cardData = setupCardData({ includeProgramEnrollment: true })
invariant(cardData.courseProgramEnrollment)
cardData.courseProgramEnrollment.enrollment_mode = "verified"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ import { RiAwardFill, RiMore2Line } from "@remixicon/react"
import NiceModal from "@ebay/nice-modal-react"
import { UnenrollProgramDialog } from "./DashboardDialogs"
import { ProgramCertificateButton } from "./ProgramEnrollmentDisplay"
import { useFeatureFlagEnabled } from "posthog-js/react"
import { FeatureFlags } from "@/common/feature_flags"
import { programPageView } from "@/common/urls"

const ProgramCardRoot = styled.div(({ theme }) => ({
Expand Down Expand Up @@ -284,15 +282,12 @@ const getContextMenuItems = (
resource: ProgramAsCourse,
enrollmentMode: string | null | undefined,
additionalItems: SimpleMenuItem[] = [],
useProductPages = false,
) => {
const menuItems = []
const detailsUrl = useProductPages
? programPageView({
readable_id: resource.readable_id,
display_mode: "course",
})
: mitxonlineLegacyUrl(`/programs/${resource.readable_id}`)
const detailsUrl = programPageView({
readable_id: resource.readable_id,
display_mode: "course",
})

const courseMenuItems = []

Expand Down Expand Up @@ -405,9 +400,6 @@ const ProgramAsCourseCard: React.FC<ProgramAsCourseCardProps> = ({
className,
onUpgradeError,
}) => {
const useProductPages = useFeatureFlagEnabled(
FeatureFlags.MitxOnlineProductPages,
)
const moduleRequirementSection = courseProgram?.req_tree?.find(
(node) => node.data.node_type === "operator",
)
Expand Down Expand Up @@ -488,7 +480,6 @@ const ProgramAsCourseCard: React.FC<ProgramAsCourseCardProps> = ({
courseProgram,
courseProgramEnrollment?.enrollment_mode,
contextMenuItems,
useProductPages ?? false,
)

const contextMenu = (
Expand Down
Loading
Loading