Skip to content

Port latest master to frontend-base (incl. Redux-to-React-Query migration)#801

Open
arbrandes wants to merge 25 commits intoopenedx:frontend-basefrom
arbrandes:frontend-base-master-ports
Open

Port latest master to frontend-base (incl. Redux-to-React-Query migration)#801
arbrandes wants to merge 25 commits intoopenedx:frontend-basefrom
arbrandes:frontend-base-master-ports

Conversation

@arbrandes
Copy link
Contributor

@arbrandes arbrandes commented Mar 4, 2026

Description

This does what it says on the tin: the main intent is to port the latest master into frontend-base land. However, since these are ports, fixes and improvements related to the changes were added beyond what's in master, as seen below. These show as separate commits so we can decide whether to backport them later.

Headliners

  • Ports 7 pending master commits to the frontend-base branch: dependency cleanups, translation support, unenrollment improvements, credit purchase URL logic, documentation fixes, and new unenrollment survey toggle variable
  • Ports the Redux to React Query migration from master, adapting it to frontend-base conventions (@src/ imports, @openedx/frontend-base APIs, slots/widgets structure)
  • Fixes a circular dependency in site.config.test.tsx that broke the entire test suite (corresponding PR to docs)
  • Runs tsc-alias only after copying assets to /dist/, otherwise SVG imports are not converted to relative paths
  • Other bug fixes:
    • Add staleTime (5 min) to prevent excessive refetching across 15+ consuming components
    • Remove redundant manual refetch on unenroll (React Query's invalidateQueries handles it)
    • Compute Date.now() per call instead of at module load (was producing stale timestamps)
    • Close modals only after mutation succeeds, not optimistically on submit
  • Improvements to the React Query implementation:
    • Performance: transform course data once in the queryFn instead of per card render
    • Simplification: replace BackedDataProvider with direct React Query cache lookups
    • Disambiguation/clarity: use explicit query key for initializeBase invalidation
    • Better UX: add smart retry that skips 4xx errors but retries server/network errors
    • Right tool for the job: separate mutation keys from query keys
    • Simplification: replace useReducer with useState in context providers
    • Better testing: add normal user happy path test for useInitializeLearnerHome

LLM usage notice

Built with assistance from Claude models (mostly Opus 4.6).

Work Log

Porting master commits

Six pre-existing master commits were cherry-picked onto frontend-base in a prior session, forming the base of the frontend-base-master-ports branch:

Circular dependency fix (site.config.test.tsx)

Running npm run test revealed that every test suite failed with EnvironmentTypes being undefined. Root cause: site.config.test.tsx imported { EnvironmentTypes } from @openedx/frontend-base, creating a circular dependency chain (initialize.jssite.config@openedx/frontend-base → still loading). Fixed by using import type { SiteConfig } (erased at compile time) and the string literal 'test' instead of EnvironmentTypes.TEST.

Redux to React Query migration port

The large migration commit from master was ported and adapted to frontend-base conventions:

  • Replaced @edx/frontend-platform references with @openedx/frontend-base equivalents
  • Fixed bare 'utils' imports to use @src/utils
  • Used useAuthenticatedUser() hook instead of the removed AppContext pattern
  • Moved test files to match widget directory structure (containers/widgets/)
  • Removed orphaned test files for deleted source modules

Fixing 13 failing test suites

After porting the migration, 13 test suites (43 tests) failed. Issues were categorized and fixed:

  1. Orphaned tests (2 suites): Deleted AppWrapper/index.test.tsx (no source file); moved ConfirmEmailBanner/hooks.test.jsx to correct widgets/ path
  2. Dead plugin-framework mock (2 suites): Removed jest.mock('@openedx/frontend-plugin-framework') from CoursesPanel and DashboardLayout tests
  3. @src/ asset resolution (2 suites): Post-processed jest.config.js to prepend @src/-prefixed .svg/.png patterns before the @src path resolver (webpack-merge ordering issue)
  4. Missing <MemoryRouter> (2 suites): Added MemoryRouter wrapper for Slot component's useLocation() dependency in 4 test files
  5. AppContext not in frontend-base (1 suite): Changed CreditBanner/views/hooks.js from AppContext + useContext() to useAuthenticatedUser() hook
  6. Duplicate jest.mock (1 suite): Merged two jest.mock('@openedx/frontend-base') calls in SelectSessionModal/hooks.test.jsx
  7. Bare 'utils' import (1 suite): Fixed 4 files to use '@src/utils'
  8. Read-only const reassignment (1 suite): Rewrote api.test.tsx to provide mock values in jest.mock() factories
  9. Wrong provider in context test (1 suite): Removed useMasquerade/useBackedData checks from context/index.test.tsx (those providers aren't in ContextProviders)

Also fixed DashboardLayout.test.jsx Slot assertion — Slot renders children directly without a wrapper div.

Bug fixes from code review

A holistic code review of the migration identified 14 issues, including by not limited to:

  • staleTime (4ec911e): Added 5-minute staleTime to the main query to prevent redundant refetches when 15+ components mount with the same query
  • Redundant refetch (498f182): Removed manual queryClient.invalidateQueries() + refetch() on unenroll — React Query's mutation onSuccess with invalidateQueries already handles this
  • Stale Date.now() (1126c07): Date.now() was computed once at module load and reused for all API calls, producing stale timestamps. Changed to compute per call
  • Optimistic modal close (2126010): Modals were closed on form submit before knowing if the mutation succeeded. Moved close logic to the mutation's onSuccess callback

Commit-by-commit test verification

Each commit was tested individually by stepping through the history with git checkout. One test failure was found and fixed: the masquerade fallback test used gcTime: 0 in its test QueryClient, which caused pre-seeded cache data to be garbage collected before queryClient.getQueryData() could read it. Fixed by removing gcTime: 0 from that specific test's QueryClient.

Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
mubbsharanwar and others added 5 commits March 4, 2026 21:01
Co-authored-by: Deborah Kaplan <deborahgu@users.noreply.github.com>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
…extra repositories (openedx#752)

Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
@arbrandes arbrandes force-pushed the frontend-base-master-ports branch 2 times, most recently from 1b7091b to 39b38c8 Compare March 5, 2026 00:38
@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 95.36680% with 24 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (frontend-base@0632525). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...gets/LearnerDashboardHeader/MasqueradeBar/hooks.js 0.00% 9 Missing and 5 partials ⚠️
...components/CourseCardBanners/CertificateBanner.jsx 82.35% 3 Missing ⚠️
...Card/components/CourseCardBanners/CourseBanner.jsx 71.42% 2 Missing ⚠️
...ners/CourseFilterControls/CourseFilterControls.jsx 88.23% 2 Missing ⚠️
...components/CourseCardBanners/CreditBanner/hooks.js 90.00% 1 Missing ⚠️
...ners/CourseCard/components/CourseCardMenu/hooks.js 94.11% 1 Missing ⚠️
src/containers/CoursesPanel/index.jsx 91.66% 1 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff                @@
##             frontend-base     #801   +/-   ##
================================================
  Coverage                 ?   90.38%           
================================================
  Files                    ?      152           
  Lines                    ?     1290           
  Branches                 ?      267           
================================================
  Hits                     ?     1166           
  Misses                   ?      119           
  Partials                 ?        5           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Use 'test' string literal instead of EnvironmentTypes.TEST and
import only the type to avoid circular dependency when mocking
@openedx/frontend-base itself.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arbrandes arbrandes force-pushed the frontend-base-master-ports branch 2 times, most recently from f574820 to 55f36bb Compare March 5, 2026 11:55
@arbrandes arbrandes force-pushed the frontend-base-master-ports branch from 55f36bb to 83dd294 Compare March 5, 2026 13:32
@arbrandes arbrandes changed the title feat: port master commits including Redux to React Query migration feat: port master commits (including react query migration) Mar 5, 2026
arbrandes and others added 8 commits March 5, 2026 11:57
Remove all Redux files and packages. Add React Query hooks, Context
providers, typed API layer, data transformers, and tests. Migrate all
components including CourseCard, CourseCardBanners, CourseFilterControls,
CoursesPanel, Dashboard, modals, and remaining widgets. Reconcile
masquerade implementations with frontend-base providers.ts architecture.

Co-Authored-By: Jacobo Dominguez <jacobo.dominguez@wgu.edu>
Co-Authored-By: Claude <noreply@anthropic.com>
useInitializeLearnerHome() is consumed by 15+ components, and with
staleTime defaulting to 0, every mount and window focus triggered a
redundant background refetch. Set staleTime to 5 minutes since
dashboard data rarely changes while the user is viewing it.

Co-Authored-By: Claude <noreply@anthropic.com>
The useUnenrollFromCourse mutation already calls invalidateQueries on
success, which triggers a refetch automatically. The manual refetch()
call in closeAndRefresh caused the API to be called twice.

Co-Authored-By: Claude <noreply@anthropic.com>
The fallback enrollment date was frozen when the module first loaded.
If the app stayed open across midnight, courses with null lastEnrolled
would get a stale timestamp.

Co-Authored-By: Claude <noreply@anthropic.com>
Previously, modals closed immediately after firing mutations via
mutate(), giving no feedback on failure. Now the close action is
passed as an onSuccess callback so the modal stays open if the
mutation fails.

Co-Authored-By: Claude <noreply@anthropic.com>
Auto-fixed stylistic issues (indentation, semicolons, brace style,
type→interface, Array<T>→T[]) and manually fixed display-name errors
(named wrapper functions), @ts-ignore@ts-expect-error, and
Function→explicit signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert GlobalDataContext.jsx to .tsx with proper interface so
setEmailConfirmation and setPlatformSettings are typed as
Dispatch<SetStateAction<T>> | null instead of literal null.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The tsconfig declares "types": ["jest"] but @types/jest was not
installed, so TypeScript treated jest as a namespace rather than a
value.  Adding the matching type definitions lets the webpack
type-checker process test files without errors.

Also bump frontend-base to fix the @src alias in jest config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arbrandes arbrandes force-pushed the frontend-base-master-ports branch from 83dd294 to 4cb9aa3 Compare March 5, 2026 14:58
arbrandes and others added 3 commits March 5, 2026 11:59
Previously, each useCourseData(cardId) call independently transformed
the full course list via getTransformedCourseDataList just to find one
item. With N course cards, this ran N times per render cycle.

Now the transformation runs once in the queryFn, stored as
coursesByCardId on the cached data. useCourseData does an O(1) lookup
and CoursesPanel reads directly from the pre-transformed data.

Co-Authored-By: Claude <noreply@anthropic.com>
The masquerade fallback data was maintained in a separate context
provider, duplicating what React Query already caches. Now when
masquerading fails, the normal user's data is read directly from the
query cache via queryClient.getQueryData(). This eliminates
BackedDataProvider and its associated useEffect sync.

Co-Authored-By: Claude <noreply@anthropic.com>
Mutations were invalidating with initialize() which produces a key
with trailing undefined, relying on React Query implementation details
for prefix matching. Added initializeBase() that produces a clean
prefix key ['learner-dashboard', 'initialize'] for unambiguous
invalidation of all initialize queries regardless of masquerade user.

Co-Authored-By: Claude <noreply@anthropic.com>
arbrandes and others added 5 commits March 5, 2026 13:35
… errors

Client errors (4xx) won't resolve on retry, so skip them. Server errors
(5xx) and network failures get up to 3 retries with React Query's
default exponential backoff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split learnerDashboardQueryKeys into distinct query and mutation key
factories for clearer semantics. Mutation keys now live in
learnerDashboardMutationKeys while query invalidation still uses
learnerDashboardQueryKeys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MasqueradeProvider and FiltersProvider used useReducer with action
type enums for what are simple value states. Replaced with useState
for less boilerplate while preserving the same public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies the basic flow: API is called without masquerade user,
data is returned with coursesByCardId transformation applied.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@arbrandes arbrandes changed the title feat: port master commits (including react query migration) feat: port master commits (including React Query migration) Mar 5, 2026
@arbrandes arbrandes changed the title feat: port master commits (including React Query migration) Port latest master to frontend-base (incl. Redux-to-React-Query migration) Mar 6, 2026
marslanabdulrauf and others added 2 commits March 9, 2026 11:42
Port of master PR openedx#738 to frontend-base.

Differences from master:
- Config added to src/app.ts instead of .env files and src/config/index.js
  (frontend-base convention)
- Uses useAppConfig() hook instead of importing configuration object
  directly (idiomatic for code running inside the provider tree)
- Default is false (master defaults to true)

Co-Authored-By: Adolfo R. Brandes <adolfo@opencraft.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s/ dirs

Move the asset copy step before tsc-alias so that .svg and .png files
exist in dist/ when tsc-alias resolves @src path aliases. Also restrict
image file copying to files under assets/ directories, excluding
screenshots in slots/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

7 participants