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
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.47.7
--------------

- remove org dashboard feature flag (#2654)
- Avoid double slashes in paths (#2652)
- use next_start_date when available in learning resource drawer (#2619)
- Fix a BannerPage background issue (#2651)
- Don't publish Professional Ed resources with blank run ids (#2637)

Version 0.47.5 (Released October 28, 2025)
--------------

Expand Down
2 changes: 1 addition & 1 deletion env/frontend.env
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
NODE_ENV=development
PORT=8062
SENTRY_ENV=dev # Re-enable sentry
NEXT_PUBLIC_OPTIMIZE_IMAGES="true"
NEXT_PUBLIC_OPTIMIZE_IMAGES="false"

# Environment variables with `NEXT_PUBLIC_` prefix are exposed to the client side
NEXT_PUBLIC_ORIGIN=${MITOL_APP_BASE_URL}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,11 @@ import {
} from "@/common/urls"
import { faker } from "@faker-js/faker/locale/en"
import invariant from "tiny-invariant"
import { useFeatureFlagEnabled } from "posthog-js/react"
import { OrganizationPage, ContractPage } from "@mitodl/mitxonline-api-axios/v2"

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

describe("DashboardLayout", () => {
beforeEach(() => {
mockedUseFeatureFlagEnabled.mockReturnValue(false)
})

type SetupOptions = {
initialUrl?: string
organizations?: OrganizationPage[]
Expand Down Expand Up @@ -68,9 +62,6 @@ describe("DashboardLayout", () => {
})

test("Renders the expected tab links and labels", async () => {
// Enable organization dashboard feature flag for this test
mockedUseFeatureFlagEnabled.mockReturnValue(true)

const organizations = [
mitxOnlineFactories.organizations.organization({
slug: "org-test-org",
Expand Down
18 changes: 4 additions & 14 deletions frontends/main/src/app-pages/DashboardPage/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ import {
import dynamic from "next/dynamic"
import { MitxOnlineUser, mitxUserQueries } from "api/mitxonline-hooks/user"
import { useUserMe } from "api/hooks/user"
import { useFeatureFlagEnabled } from "posthog-js/react"
import { FeatureFlags } from "@/common/feature_flags"
import { contractQueries } from "api/mitxonline-hooks/contracts"
import { useQuery } from "@tanstack/react-query"
import { ContractPage } from "@mitodl/mitxonline-api-axios/v2"
Expand Down Expand Up @@ -241,12 +239,11 @@ type TabData = {
}
}
const getTabData = (
orgsEnabled: boolean = false,
user?: MitxOnlineUser,
contracts?: ContractPage[],
): TabData[] => {
const orgTabs =
orgsEnabled && user && contracts
user && contracts
? user?.b2b_organizations.map((org) => {
const orgContracts = contracts?.filter(
(contract) => contract.organization === org.id,
Expand Down Expand Up @@ -317,7 +314,6 @@ const DashboardPage: React.FC<{
}> = ({ children }) => {
const pathname = usePathname()
const { isLoading: isLoadingUser, data: user } = useUserMe()
const orgsEnabled = useFeatureFlagEnabled(FeatureFlags.OrganizationDashboard)
const { data: mitxOnlineUser, isLoading: isLoadingMitxOnlineUser } = useQuery(
{
...mitxUserQueries.me(),
Expand All @@ -330,15 +326,9 @@ const DashboardPage: React.FC<{
const tabData = useMemo(
() =>
isLoadingMitxOnlineUser || isLoadingContracts
? getTabData(orgsEnabled)
: getTabData(orgsEnabled, mitxOnlineUser, contracts),
[
isLoadingMitxOnlineUser,
isLoadingContracts,
orgsEnabled,
mitxOnlineUser,
contracts,
],
? getTabData()
: getTabData(mitxOnlineUser, contracts),
[isLoadingMitxOnlineUser, isLoadingContracts, mitxOnlineUser, contracts],
)

const tabValue = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ describe("HomeContent", () => {
setupAPIs()
mockedUseFeatureFlagEnabled.mockImplementation((flag) => {
if (flag === "enrollment-dashboard") return enrollmentsEnabled
if (flag === "mitlearn-organization-dashboard") return false // Disable org cards to avoid image issues
return false
})

Expand Down
3 changes: 1 addition & 2 deletions frontends/main/src/app-pages/DashboardPage/HomeContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ const HomeContent: React.FC = () => {
const { isLoading: isLoadingProfile, data: user } = useUserMe()
const topics = user?.profile?.preference_search_filters.topic
const certification = user?.profile?.preference_search_filters.certification
const showOrgs = useFeatureFlagEnabled(FeatureFlags.OrganizationDashboard)
const showEnrollments = useFeatureFlagEnabled(
FeatureFlags.EnrollmentDashboard,
)
Expand All @@ -89,7 +88,7 @@ const HomeContent: React.FC = () => {
</ButtonLink>
</HomeHeaderRight>
</HomeHeader>
{showOrgs ? <OrganizationCards /> : null}
<OrganizationCards />
{showEnrollments ? <EnrollmentDisplay /> : null}
<Suspense>
<StyledResourceCarousel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { renderWithProviders, screen, within, waitFor } from "@/test-utils"
import OrganizationContent from "./OrganizationContent"
import { setMockResponse } from "api/test-utils"
import { urls, factories } from "api/mitxonline-test-utils"
import { useFeatureFlagEnabled } from "posthog-js/react"
import {
organizationCoursesWithContracts,
mitxonlineProgram,
Expand All @@ -23,14 +22,8 @@ import { faker } from "@faker-js/faker/locale/en"
const makeCourseEnrollment = factories.enrollment.courseEnrollment
const makeGrade = factories.enrollment.grade

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

describe("OrganizationContent", () => {
beforeEach(() => {
mockedUseFeatureFlagEnabled.mockReturnValue(true)
setMockResponse.get(urls.enrollment.enrollmentsList(), [])
setMockResponse.get(urls.enrollment.enrollmentsListV2(), [])
setMockResponse.get(urls.programEnrollments.enrollmentsList(), [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import React, { useEffect } from "react"
import DOMPurify from "isomorphic-dompurify"
import Image from "next/image"
import { useFeatureFlagEnabled } from "posthog-js/react"
import { FeatureFlags } from "@/common/feature_flags"
import { useQueries, useQuery } from "@tanstack/react-query"
import {
programsQueries,
Expand Down Expand Up @@ -360,9 +358,6 @@ type OrganizationContentInternalProps = {
const OrganizationContentInternal: React.FC<
OrganizationContentInternalProps
> = ({ org }) => {
const isOrgDashboardEnabled = useFeatureFlagEnabled(
FeatureFlags.OrganizationDashboard,
)
const orgId = org.id
const contracts = useQuery(contractQueries.contractsList())
const orgContracts = contracts.data?.filter(
Expand All @@ -379,8 +374,6 @@ const OrganizationContentInternal: React.FC<
programCollectionQueries.programCollectionsList({}),
)

if (!isOrgDashboardEnabled) return null

const transformedPrograms = programs.data?.results
.filter((program) => program.collections.length === 0)
.map((program) => transform.mitxonlineProgram(program))
Expand Down
1 change: 0 additions & 1 deletion frontends/main/src/common/feature_flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export enum FeatureFlags {
RecommendationBot = "recommendation-bot",
HomePageRecommendationBot = "home-page-recommendation-bot",
EnrollmentDashboard = "enrollment-dashboard",
OrganizationDashboard = "mitlearn-organization-dashboard",
VideoShorts = "video-shorts",
ProductPageCourse = "product-page-course",
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,35 @@ describe("Learning resource info section start date", () => {
within(section).getByText(runDate)
})

test("Uses next_start_date when available", () => {
const course = {
...courses.free.dated,
next_start_date: "2024-03-15T00:00:00Z",
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText("March 15, 2024")
})

test("Falls back to run date when next_start_date is null", () => {
const course = {
...courses.free.dated,
next_start_date: null,
}
const run = course.runs?.[0]
invariant(run)
const runDate = formatRunDate(run, false)
invariant(runDate)
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText(runDate)
expect(within(section).queryByText("March 15, 2024")).toBeNull()
})

test("As taught in date(s)", () => {
const course = courses.free.anytime
const run = course.runs?.[0]
Expand Down Expand Up @@ -151,6 +180,31 @@ describe("Learning resource info section start date", () => {
})
})

test("Multiple run dates with next_start_date uses next_start_date as first date", () => {
const course = {
...courses.multipleRuns.sameData,
next_start_date: "2024-01-15T00:00:00Z",
}
const sortedDates = course.runs
?.sort((a, b) => {
if (a?.start_date && b?.start_date) {
return Date.parse(a.start_date) - Date.parse(b.start_date)
}
return 0
})
.map((run) => formatRunDate(run, false))
.filter((date) => date !== null)

// First date should be next_start_date, second should be original second date
const expectedDateText = `January 15, 2024${SEPARATOR}${sortedDates?.[1]}Show more`
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getAllByText((_content, node) => {
return node?.textContent === expectedDateText || false
})
})

test("If data is different then dates, formats, locations and prices are not shown", () => {
const course = courses.multipleRuns.differentData
renderWithTheme(<InfoSection resource={course} />)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getLearningResourcePrices,
showStartAnytime,
NoSSR,
formatDate,
} from "ol-utilities"
import { theme, Link } from "ol-components"
import DifferingRunsTable from "./DifferingRunsTable"
Expand Down Expand Up @@ -179,7 +180,7 @@ const totalRunsWithDates = (resource: LearningResource) => {

const RunDates: React.FC<{ resource: LearningResource }> = ({ resource }) => {
const [showingMore, setShowingMore] = useState(false)
const sortedDates = resource.runs
let sortedDates = resource.runs
?.sort((a, b) => {
if (a?.start_date && b?.start_date) {
return Date.parse(a.start_date) - Date.parse(b.start_date)
Expand All @@ -188,6 +189,18 @@ const RunDates: React.FC<{ resource: LearningResource }> = ({ resource }) => {
})
.map((run) => formatRunDate(run, showStartAnytime(resource)))
.filter((date) => date !== null)

const nextStartDate = resource.next_start_date
? formatDate(resource.next_start_date, "MMMM DD, YYYY")
: null

if (sortedDates && nextStartDate) {
// Replace the first date with next_start_date
sortedDates = [nextStartDate, ...sortedDates.slice(1)]
}
if (!sortedDates || sortedDates.length === 0) {
return null
}
const totalDates = sortedDates?.length || 0
const showMore = totalDates > 2
if (showMore) {
Expand Down
14 changes: 11 additions & 3 deletions frontends/ol-components/src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ const SubHeader = styled(Typography)({
})

type BannerBackgroundProps = {
/**
* Background image src, url(...), or image-set(...).
*/
backgroundUrl?: string
backgroundSize?: string
backgroundDim?: number
}

const standardizeBackgroundUrl = (url: string) => {
if (url.startsWith("url(") || url.startsWith("image-set(")) {
return url
}
return url.startsWith("image-set(") ? url : `url('${url}')`
}

/**
* This is a full-width banner component that takes a background image URL.
*/
Expand All @@ -26,9 +36,7 @@ const BannerBackground = styled.div<BannerBackgroundProps>(
backgroundSize = "cover",
backgroundDim = 0,
}) => {
const backgroundUrlFn = backgroundUrl.startsWith("image-set(")
? backgroundUrl
: `url('${backgroundUrl}')`
const backgroundUrlFn = standardizeBackgroundUrl(backgroundUrl)

return {
backgroundAttachment: "fixed",
Expand Down
17 changes: 10 additions & 7 deletions learning_resources/etl/mitpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,13 @@ def _transform_runs(resource_data: dict) -> list[dict]:
duration = parse_resource_duration(resource_data.get("duration"))
published_runs = []
for run_data in runs_data:
run_id = run_data[0]
start = parse_date(run_data[1])
end = parse_date(run_data[2])
enrollment_end = parse_date(run_data[3])
published = (not end and not enrollment_end) or (now <= (enrollment_end or end))
published = run_id and (
(not end and not enrollment_end) or (now <= (enrollment_end or end))
)
if published:
published_runs.append(
{
Expand Down Expand Up @@ -283,8 +286,8 @@ def transform_course(resource_data: dict) -> dict or None:
Returns:
dict: transformed course data if it has any viable runs
"""
runs = _transform_runs(resource_data)
if runs:
published_runs = _transform_runs(resource_data)
if published_runs:
return {
"readable_id": resource_data["uuid"],
"offered_by": copy.deepcopy(OFFERED_BY),
Expand All @@ -303,7 +306,7 @@ def transform_course(resource_data: dict) -> dict or None:
"delivery": transform_delivery(resource_data["learning_format"]),
"published": True,
"topics": parse_topics(resource_data),
"runs": runs,
"runs": published_runs,
"format": [Format.asynchronous.name],
"pace": [Pace.instructor_paced.name],
"availability": Availability.dated.name,
Expand All @@ -321,8 +324,8 @@ def transform_program(resource_data: dict) -> dict or None:
Returns:
dict: transformed program data
"""
runs = _transform_runs(resource_data)
if runs:
published_runs = _transform_runs(resource_data)
if published_runs:
return {
"readable_id": resource_data["uuid"],
"offered_by": copy.deepcopy(OFFERED_BY),
Expand All @@ -343,7 +346,7 @@ def transform_program(resource_data: dict) -> dict or None:
"delivery": transform_delivery(resource_data["learning_format"]),
"published": True,
"topics": parse_topics(resource_data),
"runs": runs,
"runs": published_runs,
"format": [Format.asynchronous.name],
"pace": [Pace.instructor_paced.name],
"availability": Availability.dated.name,
Expand Down
Loading
Loading