From 31907fbc4a407fae192c495955a0f390e3e0eb26 Mon Sep 17 00:00:00 2001 From: farhan Date: Mon, 8 Jun 2026 14:01:54 +0500 Subject: [PATCH 1/3] refactor: remove legacy waffle flags, use MFE routes exclusively Removes all conditional routing based on deprecated waffle flags (useNewCourseOutlinePage, useNewUnitPage, useNewFilesUploadsPage, useNewTextbooksPage, useNewCustomPages, useNewVideoUploadsPage, useNewImportPage, useNewExportPage, useNewUpdatesPage, useNewHomePage, useNewPdfEditor). MFE paths are now always used. Co-Authored-By: Claude Sonnet 4.6 --- .../course-apps/proctoring/Settings.test.jsx | 5 ++-- src/CourseAuthoringContext.tsx | 20 +++------------ .../ChecklistSection/ChecklistItemBody.jsx | 10 +++----- .../ChecklistSection/ChecklistItemComment.jsx | 8 +----- .../add-component/AddComponent.test.tsx | 15 ----------- .../add-component/AddComponent.tsx | 6 ++--- .../breadcrumbs/Breadcrumbs.test.tsx | 21 ---------------- src/course-unit/breadcrumbs/Breadcrumbs.tsx | 14 ++--------- src/custom-pages/CustomPages.tsx | 7 +----- .../components/VideoEditorModal.tsx | 3 --- .../VideoSettingsModal/index.test.tsx | 3 +-- .../components/VideoSettingsModal/index.tsx | 4 +-- src/header/Header.tsx | 7 ++---- src/header/hooks.test.tsx | 6 ++--- src/header/hooks.tsx | 20 ++++++--------- .../pages/PageSettingButton.jsx | 24 ++++-------------- .../pages/PageSettingButton.test.jsx | 25 ++----------------- src/studio-home/card-item/index.tsx | 4 +-- src/textbooks/Textbooks.tsx | 2 +- src/textbooks/hooks.tsx | 7 +----- 20 files changed, 40 insertions(+), 171 deletions(-) diff --git a/plugins/course-apps/proctoring/Settings.test.jsx b/plugins/course-apps/proctoring/Settings.test.jsx index a2148e89e2..6a07c7143c 100644 --- a/plugins/course-apps/proctoring/Settings.test.jsx +++ b/plugins/course-apps/proctoring/Settings.test.jsx @@ -471,9 +471,8 @@ describe('ProctoredExamSettings', () => { screen.getByDisplayValue('mockproc'); }); // (1) for studio settings - // (2) waffle flags - // (3) for course details - expect(axiosMock.history.get.length).toBe(3); + // (2) for course details + expect(axiosMock.history.get.length).toBe(2); expect(axiosMock.history.get[0].url.includes('proctored_exam_settings')).toEqual(true); }); diff --git a/src/CourseAuthoringContext.tsx b/src/CourseAuthoringContext.tsx index a3101eb5f1..c980e11829 100644 --- a/src/CourseAuthoringContext.tsx +++ b/src/CourseAuthoringContext.tsx @@ -1,4 +1,3 @@ -import { getConfig } from '@edx/frontend-platform'; import { createContext, useContext, @@ -10,7 +9,7 @@ import { useNavigate } from 'react-router'; import { useToggleWithValue } from '@src/hooks'; import { type UnitXBlock, type XBlock } from '@src/data/types'; import { CourseDetailsData } from './data/api'; -import { useCourseDetails, useWaffleFlags } from './data/apiHooks'; +import { useCourseDetails } from './data/apiHooks'; import { RequestStatusType } from './data/constants'; import { getOutlineIndexData } from './course-outline/data/selectors'; @@ -53,7 +52,6 @@ export const CourseAuthoringProvider = ({ courseId, }: CourseAuthoringProviderProps) => { const navigate = useNavigate(); - const waffleFlags = useWaffleFlags(); const { data: courseDetails, status: courseDetailStatus } = useCourseDetails(courseId); const canChangeProviders = getAuthenticatedUser().administrator || new Date(courseDetails?.start ?? 0) > new Date(); const { courseStructure } = useSelector(getOutlineIndexData); @@ -65,25 +63,13 @@ export const CourseAuthoringProvider = ({ closeUnlinkModal, ] = useToggleWithValue(); - const getUnitUrl = (locator: string) => { - if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) { - // instanbul ignore next - return `/course/${courseId}/container/${locator}`; - } - return `${getConfig().STUDIO_BASE_URL}/container/${locator}`; - }; + const getUnitUrl = (locator: string) => `/course/${courseId}/container/${locator}`; /** * Open the unit page for a given locator. */ const openUnitPage = async (locator: string) => { - const url = getUnitUrl(locator); - if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) { - // instanbul ignore next - navigate(url); - } else { - window.location.assign(url); - } + navigate(getUnitUrl(locator)); }; const context = useMemo(() => ({ diff --git a/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx b/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx index 8fc0c1643a..79cde9a4ea 100644 --- a/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx @@ -5,13 +5,10 @@ import { ActionRow, Button, Icon } from '@openedx/paragon'; import { CheckCircle, RadioButtonUnchecked } from '@openedx/paragon/icons'; import { getConfig } from '@edx/frontend-platform'; -import { useWaffleFlags } from '@src/data/apiHooks'; - import messages from './messages'; -const getUpdateLinks = (courseId, waffleFlags) => { +const getUpdateLinks = (courseId) => { const baseUrl = getConfig().STUDIO_BASE_URL; - const isLegacyOutlineUrl = !waffleFlags.useNewCourseOutlinePage; return { welcomeMessage: `/course/${courseId}/course_info`, @@ -19,7 +16,7 @@ const getUpdateLinks = (courseId, waffleFlags) => { certificate: `/course/${courseId}/certificates`, courseDates: `/course/${courseId}/settings/details/#schedule`, proctoringEmail: `${baseUrl}/pages-and-resources/proctoring/settings`, - outline: isLegacyOutlineUrl ? `${baseUrl}/course/${courseId}` : `/course/${courseId}`, + outline: `/course/${courseId}`, }; }; @@ -29,8 +26,7 @@ const ChecklistItemBody = ({ isCompleted, }) => { const intl = useIntl(); - const waffleFlags = useWaffleFlags(courseId); - const updateLinks = getUpdateLinks(courseId, waffleFlags); + const updateLinks = getUpdateLinks(courseId); return ( diff --git a/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx b/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx index aa0e3f3010..b2c8ec234a 100644 --- a/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx @@ -3,8 +3,6 @@ import { FormattedMessage, FormattedNumber } from '@edx/frontend-platform/i18n'; import { Icon } from '@openedx/paragon'; import { Link } from 'react-router-dom'; import { ModeComment } from '@openedx/paragon/icons'; -import { getConfig } from '@edx/frontend-platform'; -import { useWaffleFlags } from '../../data/apiHooks'; import messages from './messages'; const ChecklistItemComment = ({ @@ -12,11 +10,7 @@ const ChecklistItemComment = ({ checkId, data, }) => { - const waffleFlags = useWaffleFlags(courseId); - - const getPathToCourseOutlinePage = (assignmentId) => (waffleFlags.useNewCourseOutlinePage - ? `/course/${courseId}#${assignmentId}` : - `${getConfig().STUDIO_BASE_URL}/course/${courseId}#${assignmentId}`); + const getPathToCourseOutlinePage = (assignmentId) => `/course/${courseId}#${assignmentId}`; const commentWrapper = (comment) => (
diff --git a/src/course-unit/add-component/AddComponent.test.tsx b/src/course-unit/add-component/AddComponent.test.tsx index a44d44917b..8bcbdf7603 100644 --- a/src/course-unit/add-component/AddComponent.test.tsx +++ b/src/course-unit/add-component/AddComponent.test.tsx @@ -2,7 +2,6 @@ /* eslint-disable react/prop-types */ import userEvent, { UserEvent } from '@testing-library/user-event'; -import { mockWaffleFlags } from '@src/data/apiHooks.mock'; import { RenderResult } from '@testing-library/react'; import { act, @@ -360,20 +359,6 @@ describe('', () => { }, expect.any(Function)); }); - it('adds a PDF block and launches the legacy iframe editor', async () => { - const user = userEvent.setup(); - mockWaffleFlags({ useNewPdfEditor: false }); - const { getByRole, queryAllByRole } = renderComponent(); - await createPdfBlock({ getByRole, queryAllByRole, user }); - expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled(); - expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({ - parentLocator: '123', - type: COMPONENT_TYPES.pdf, - // Setting the category and not supplying an additional function launches the traditional editor. - category: COMPONENT_TYPES.pdf, - }); - }); - it('verifies "Text" component selection in modal', async () => { const user = userEvent.setup(); const { getByRole, getByText } = renderComponent(); diff --git a/src/course-unit/add-component/AddComponent.tsx b/src/course-unit/add-component/AddComponent.tsx index 9dd8078f41..cbb0134913 100644 --- a/src/course-unit/add-component/AddComponent.tsx +++ b/src/course-unit/add-component/AddComponent.tsx @@ -86,7 +86,7 @@ const AddComponent = ({ const [selectedComponents, setSelectedComponents] = useState([]); const [usageId, setUsageId] = useState(null); const { sendMessageToIframe } = useIframe(); - const { useVideoGalleryFlow, useNewPdfEditor } = useWaffleFlags(courseId ?? undefined); + const { useVideoGalleryFlow } = useWaffleFlags(courseId ?? undefined); const courseUnit = useSelector(getCourseUnitData); const sequenceId = courseUnit?.ancestorInfo?.ancestors?.[0]?.id; @@ -181,13 +181,13 @@ const AddComponent = ({ // *in code* and not just in UI seems like a mistake in retrospect. // // There will be more of these, and soon. - if (moduleName === COMPONENT_TYPES.pdf && useNewPdfEditor) { + if (moduleName === COMPONENT_TYPES.pdf) { handleCreateNewCourseXBlock( { type: moduleName, parentLocator: blockId }, /* istanbul ignore next */ ({ courseKey, locator }) => { setCourseId(courseKey); - setBlockType(moduleName); + setBlockType(moduleName ?? null); setNewBlockId(locator); showXBlockEditorModal(); }, diff --git a/src/course-unit/breadcrumbs/Breadcrumbs.test.tsx b/src/course-unit/breadcrumbs/Breadcrumbs.test.tsx index 5f47f25251..8a8a278b7f 100644 --- a/src/course-unit/breadcrumbs/Breadcrumbs.test.tsx +++ b/src/course-unit/breadcrumbs/Breadcrumbs.test.tsx @@ -1,5 +1,4 @@ import userEvent from '@testing-library/user-event'; -import { getConfig } from '@edx/frontend-platform'; import { initializeMocks, waitFor, @@ -150,24 +149,4 @@ describe('', () => { await user.click(dropdownItem); expect(dropdownItem).toHaveAttribute('href', url); }); - - it('falls back to window.location.href when the waffle flag is disabled', async () => { - const user = userEvent.setup(); - // eslint-disable-next-line @typescript-eslint/naming-convention - const { ancestor_xblocks: [{ children: [{ display_name, url }] }] } = courseSectionVerticalMock; - axiosMock - .onGet(getApiWaffleFlagsUrl(courseId)) - .reply(200, { useNewCourseOutlinePage: false }); - - const { getByText, getByRole } = renderComponent(); - - const dropdownBtn = getByText(breadcrumbsExpected.section.displayName); - await user.click(dropdownBtn); - - const dropdownItem = getByRole('link', { name: display_name }); - // We need waitFor here because the waffle flag defaults to true but asynchronously loads false from our axiosMock - await waitFor(() => { - expect(dropdownItem).toHaveAttribute('href', `${getConfig().STUDIO_BASE_URL}${url}`); - }); - }); }); diff --git a/src/course-unit/breadcrumbs/Breadcrumbs.tsx b/src/course-unit/breadcrumbs/Breadcrumbs.tsx index 1790ff1917..ed45cbddd2 100644 --- a/src/course-unit/breadcrumbs/Breadcrumbs.tsx +++ b/src/course-unit/breadcrumbs/Breadcrumbs.tsx @@ -5,23 +5,13 @@ import { ArrowDropDown as ArrowDropDownIcon, ChevronRight as ChevronRightIcon, } from '@openedx/paragon/icons'; -import { getConfig } from '@edx/frontend-platform'; - -import { useWaffleFlags } from '../../data/apiHooks'; import { getCourseSectionVertical } from '../data/selectors'; import { adoptCourseSectionUrl, subsectionFirstUnitEditUrl } from '../utils'; const Breadcrumbs = ({ courseId, parentUnitId }: { courseId: string; parentUnitId: string; }) => { const { ancestorXblocks = [] } = useSelector(getCourseSectionVertical); - const waffleFlags = useWaffleFlags(courseId); - - const getPathToCourseOutlinePage = (url) => (waffleFlags.useNewCourseOutlinePage - ? url : - `${getConfig().STUDIO_BASE_URL}${url}`); - const getPathToCourseUnitPage = (url) => (waffleFlags.useNewUnitPage - ? adoptCourseSectionUrl({ url, courseId, parentUnitId }) - : `${getConfig().STUDIO_BASE_URL}${url}`); + const getPathToCourseUnitPage = (url) => adoptCourseSectionUrl({ url, courseId, parentUnitId }); // based on the level of breadcrumbs the url will differ // at the subsection level it should navigate to the first unit if available @@ -29,7 +19,7 @@ const Breadcrumbs = ({ courseId, parentUnitId }: { courseId: string; parentUnitI function getPathToCoursePage(index, url, usageKey: string) { let navUrl: string; if (index === 0) { - navUrl = getPathToCourseOutlinePage(url); + navUrl = url; } else if (index === 1) { navUrl = subsectionFirstUnitEditUrl({ courseId, subsectionId: usageKey }); } else { diff --git a/src/custom-pages/CustomPages.tsx b/src/custom-pages/CustomPages.tsx index dddb9091eb..3f12009ed2 100644 --- a/src/custom-pages/CustomPages.tsx +++ b/src/custom-pages/CustomPages.tsx @@ -28,7 +28,6 @@ import DraggableList, { SortableItem } from '@src/generic/DraggableList'; import ErrorAlert from '@src/editors/sharedComponents/ErrorAlerts/ErrorAlert'; import { RequestStatus } from '@src/data/constants'; import { useModels } from '@src/generic/model-store'; -import { useWaffleFlags } from '@src/data/apiHooks'; import getPageHeadTitle from '@src/generic/utils'; import { getPagePath } from '@src/utils'; import { DeprecatedReduxState } from '@src/store'; @@ -70,8 +69,6 @@ const CustomPages = () => { const deletePageStatus = useSelector((state: DeprecatedReduxState) => state.customPages.deletingStatus); const savingStatus = useSelector(getSavingStatus); const loadingStatus = useSelector(getLoadingStatus); - const waffleFlags = useWaffleFlags(courseId); - const pages = useModels('customPages', customPagesIds); const handleAddPage = () => { @@ -128,9 +125,7 @@ const CustomPages = () => { links={[ { label: 'Content', - to: waffleFlags.useNewCourseOutlinePage - ? `/course/${courseId}` - : `${config.STUDIO_BASE_URL}/course/${courseId}`, + to: `/course/${courseId}`, }, { label: 'Pages and Resources', to: getPagePath(courseId, 'true', 'tabs') }, ]} diff --git a/src/editors/containers/VideoEditor/components/VideoEditorModal.tsx b/src/editors/containers/VideoEditor/components/VideoEditorModal.tsx index 9b3b905a0c..c03d0f2639 100644 --- a/src/editors/containers/VideoEditor/components/VideoEditorModal.tsx +++ b/src/editors/containers/VideoEditor/components/VideoEditorModal.tsx @@ -1,7 +1,6 @@ import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; -import { useWaffleFlags } from '@src/data/apiHooks'; import * as appHooks from '../../../hooks'; import { thunkActions, selectors } from '../../../data/redux'; import VideoSettingsModal from './VideoSettingsModal'; @@ -42,7 +41,6 @@ const VideoEditorModal: React.FC = ({ const isLoaded = useSelector( (state) => selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchVideos }), ); - const { useNewVideoUploadsPage } = useWaffleFlags(); useEffect(() => { hooks.initialize(dispatch, selectedVideoId, selectedVideoUrl); @@ -54,7 +52,6 @@ const VideoEditorModal: React.FC = ({ onReturn: onSettingsReturn, isLibrary, onClose, - useNewVideoUploadsPage, }} /> ); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.test.tsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.test.tsx index 0635e47c9e..598ebed665 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.test.tsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.test.tsx @@ -10,7 +10,6 @@ const defaultProps = { onReturn: jest.fn(), isLibrary: false, onClose: jest.fn(), - useNewVideoUploadsPage: true, }; const renderComponent = (overrideProps = {}) => { @@ -38,7 +37,7 @@ describe('', () => { window.scrollTo = jest.fn(); }); - it('renders back button when useNewVideoUploadsPage is true and isLibrary is false', () => { + it('renders back button when isLibrary is false', () => { renderComponent(); expect(screen.getByRole('button', { name: /replace video/i })).toBeInTheDocument(); }); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.tsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.tsx index 98ff1f808f..60f754b31f 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.tsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/index.tsx @@ -21,17 +21,15 @@ interface Props { onReturn: () => void; isLibrary: boolean; onClose?: (() => void) | null; - useNewVideoUploadsPage?: boolean; } const VideoSettingsModal: React.FC = ({ onReturn, isLibrary, onClose, - useNewVideoUploadsPage, }) => ( <> - {!isLibrary && useNewVideoUploadsPage && ( + {!isLibrary && (