diff --git a/package.json b/package.json index 236d086e58..8fa50f78ea 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "extends @edx/browserslist-config" ], "scripts": { - "build": "sh run-build-for-gh-deps.sh", + "build": "fedx-scripts webpack", "i18n_extract": "fedx-scripts formatjs extract", "stylelint": "stylelint \"plugins/**/*.scss\" \"src/**/*.scss\" \"scss/**/*.scss\" --config .stylelintrc.json", "lint": "npm run stylelint && fedx-scripts eslint --ext .js --ext .jsx --ext .ts --ext .tsx .", diff --git a/run-build-for-gh-deps.sh b/run-build-for-gh-deps.sh deleted file mode 100644 index 996601553f..0000000000 --- a/run-build-for-gh-deps.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# TODO: This file is temporary and will be removed after testing - -log() { - echo "=============================== $1 ===============================" -} - -run_command() { - echo "\$ $1" - eval $1 -} - -log "Starting deployment script" -run_command "pwd" - -# frontend-component-header -log "Processing frontend-component-header" -run_command "cd node_modules/@edx/" || exit -log "Current directory: $(pwd)" -run_command "rm -rf frontend-component-header" -run_command "mkdir frontend-component-header" || exit -run_command "git clone -b Peter_Kulko/use-SPA-functionality --single-branch https://github.com/raccoongang/frontend-component-header.git frontend-component-header-temp" -run_command "cd frontend-component-header-temp" || exit -log "Current directory: $(pwd)" -run_command "npm ci" || exit -run_command "npm run build" || exit -run_command "cp -r dist ../frontend-component-header/" || exit -run_command "cp -r package.json ../frontend-component-header/" || exit -run_command "cd .." -run_command "rm -rf frontend-component-header-temp" -run_command "cd ../.." || exit -log "Current directory: $(pwd)" - -# webpack -log "Running webpack" -run_command "fedx-scripts webpack" - -log "Deployment script finished." \ No newline at end of file diff --git a/src/CourseAuthoringPage.test.jsx b/src/CourseAuthoringPage.test.jsx index c7eeeb9be8..4f342f826b 100644 --- a/src/CourseAuthoringPage.test.jsx +++ b/src/CourseAuthoringPage.test.jsx @@ -12,7 +12,8 @@ import CourseAuthoringPage from './CourseAuthoringPage'; import PagesAndResources from './pages-and-resources/PagesAndResources'; import { executeThunk } from './utils'; import { fetchCourseApps } from './pages-and-resources/data/thunks'; -import { fetchCourseDetail } from './data/thunks'; +import { fetchCourseDetail, fetchWaffleFlags } from './data/thunks'; +import { getApiWaffleFlagsUrl } from './data/api'; const courseId = 'course-v1:edX+TestX+Test_Course'; let mockPathname = '/evilguy/'; @@ -25,7 +26,7 @@ jest.mock('react-router-dom', () => ({ let axiosMock; let store; -beforeEach(() => { +beforeEach(async () => { initializeMockApp({ authenticatedUser: { userId: 3, @@ -36,6 +37,10 @@ beforeEach(() => { }); store = initializeStore(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(getApiWaffleFlagsUrl(courseId)) + .reply(200, {}); + await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); describe('Editor Pages Load no header', () => { diff --git a/src/CourseAuthoringRoutes.test.jsx b/src/CourseAuthoringRoutes.test.jsx index b72e340c16..790c68326c 100644 --- a/src/CourseAuthoringRoutes.test.jsx +++ b/src/CourseAuthoringRoutes.test.jsx @@ -3,8 +3,13 @@ import { AppProvider } from '@edx/frontend-platform/react'; import { initializeMockApp } from '@edx/frontend-platform'; import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; +import MockAdapter from 'axios-mock-adapter'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import CourseAuthoringRoutes from './CourseAuthoringRoutes'; import initializeStore from './store'; +import { executeThunk } from './utils'; +import { getApiWaffleFlagsUrl } from './data/api'; +import { fetchWaffleFlags } from './data/thunks'; const courseId = 'course-v1:edX+TestX+Test_Course'; const pagesAndResourcesMockText = 'Pages And Resources'; @@ -12,6 +17,7 @@ const editorContainerMockText = 'Editor Container'; const videoSelectorContainerMockText = 'Video Selector Container'; const customPagesMockText = 'Custom Pages'; let store; +let axiosMock; const mockComponentFn = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -50,7 +56,7 @@ jest.mock('./custom-pages/CustomPages', () => (props) => { }); describe('', () => { - beforeEach(() => { + beforeEach(async () => { initializeMockApp({ authenticatedUser: { userId: 3, @@ -60,6 +66,11 @@ describe('', () => { }, }); store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(getApiWaffleFlagsUrl(courseId)) + .reply(200, {}); + await executeThunk(fetchWaffleFlags(courseId), store.dispatch); }); fit('renders the PagesAndResources component when the pages and resources route is active', () => { diff --git a/src/course-outline/hooks.jsx b/src/course-outline/hooks.jsx index 9038a222be..739d599432 100644 --- a/src/course-outline/hooks.jsx +++ b/src/course-outline/hooks.jsx @@ -114,7 +114,7 @@ const useCourseOutline = ({ courseId }) => { }; const getUnitUrl = (locator) => { - if (getConfig().ENABLE_UNIT_PAGE === 'true' || waffleFlags.useNewUnitPage) { + if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) { return `/course/${courseId}/container/${locator}`; } return `${getConfig().STUDIO_BASE_URL}/container/${locator}`; @@ -122,7 +122,7 @@ const useCourseOutline = ({ courseId }) => { const openUnitPage = (locator) => { const url = getUnitUrl(locator); - if (getConfig().ENABLE_UNIT_PAGE === 'true' || waffleFlags.useNewUnitPage) { + if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) { navigate(url); } else { window.location.assign(url); diff --git a/src/data/api.js b/src/data/api.js index 45c533dfd2..61a0719fb9 100644 --- a/src/data/api.js +++ b/src/data/api.js @@ -1,4 +1,3 @@ -/* eslint-disable import/prefer-default-export */ import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; diff --git a/src/data/slice.js b/src/data/slice.js index 6429ea4452..5953d6d3fa 100644 --- a/src/data/slice.js +++ b/src/data/slice.js @@ -20,7 +20,7 @@ const slice = createSlice({ useNewExportPage: true, useNewFilesUploadsPage: true, useNewVideoUploadsPage: true, - useNewCourseOutlinePage: false, + useNewCourseOutlinePage: true, useNewUnitPage: false, useNewCourseTeamPage: true, useNewCertificatesPage: true, diff --git a/src/data/thunks.js b/src/data/thunks.js index 9ef5a8219a..9c30693161 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.js @@ -34,15 +34,8 @@ export function fetchWaffleFlags(courseId) { return async (dispatch) => { dispatch(updateStatus({ courseId, status: RequestStatus.IN_PROGRESS })); - try { - const waffleFlags = await getWaffleFlags(courseId); - dispatch(updateStatus({ courseId, status: RequestStatus.SUCCESSFUL })); - dispatch(fetchWaffleFlagsSuccess({ waffleFlags })); - } catch (error) { - // If fetching the waffle flags is unsuccessful, - // the pages will still be accessible and display without any issues. - // eslint-disable-next-line no-console - console.error({ courseId, status: RequestStatus.NOT_FOUND }); - } + const waffleFlags = await getWaffleFlags(courseId); + dispatch(updateStatus({ courseId, status: RequestStatus.SUCCESSFUL })); + dispatch(fetchWaffleFlagsSuccess({ waffleFlags })); }; } diff --git a/src/header/Header.tsx b/src/header/Header.tsx index bcc97e9a35..ebd3199893 100644 --- a/src/header/Header.tsx +++ b/src/header/Header.tsx @@ -3,7 +3,7 @@ import { getConfig } from '@edx/frontend-platform'; import { useIntl } from '@edx/frontend-platform/i18n'; import { StudioHeader } from '@edx/frontend-component-header'; import { type Container, useToggle } from '@openedx/paragon'; -import { generatePath, useHref, useNavigate } from 'react-router-dom'; +import { generatePath, useHref } from 'react-router-dom'; import { getWaffleFlags } from '../data/selectors'; import { SearchModal } from '../search-modal'; @@ -33,7 +33,6 @@ const Header = ({ }: HeaderProps) => { const intl = useIntl(); const libraryHref = useHref('/library/:libraryId'); - const navigate = useNavigate(); const waffleFlags = useSelector(getWaffleFlags); const [isShowSearchModalOpen, openSearchModal, closeSearchModal] = useToggle(false); @@ -80,7 +79,6 @@ const Header = ({ outlineLink={getOutlineLink()} searchButtonAction={meiliSearchEnabled ? openSearchModal : undefined} containerProps={containerProps} - onNavigate={(url) => navigate(url)} isNewHomePage={waffleFlags.useNewHomePage} /> {meiliSearchEnabled && ( diff --git a/src/studio-home/StudioHome.test.jsx b/src/studio-home/StudioHome.test.jsx index bc7ce8fbf4..b2a8fd871b 100644 --- a/src/studio-home/StudioHome.test.jsx +++ b/src/studio-home/StudioHome.test.jsx @@ -18,6 +18,8 @@ import { executeThunk } from '../utils'; import { studioHomeMock } from './__mocks__'; import { getStudioHomeApiUrl } from './data/api'; import { fetchStudioHomeData } from './data/thunks'; +import { getApiWaffleFlagsUrl } from '../data/api'; +import { fetchWaffleFlags } from '../data/thunks'; import messages from './messages'; import createNewCourseMessages from './create-new-course-form/messages'; import createOrRerunCourseMessages from '../generic/create-or-rerun-course/messages'; @@ -84,6 +86,10 @@ describe('', () => { axiosMock = new MockAdapter(getAuthenticatedHttpClient()); axiosMock.onGet(getStudioHomeApiUrl()).reply(404); await executeThunk(fetchStudioHomeData(), store.dispatch); + axiosMock + .onGet(getApiWaffleFlagsUrl()) + .reply(200, {}); + await executeThunk(fetchWaffleFlags(), store.dispatch); useSelector.mockReturnValue({ studioHomeLoadingStatus: RequestStatus.FAILED }); }); @@ -113,6 +119,10 @@ describe('', () => { axiosMock.onGet(getStudioHomeApiUrl()).reply(200, studioHomeMock); await executeThunk(fetchStudioHomeData(), store.dispatch); useSelector.mockReturnValue(studioHomeMock); + axiosMock + .onGet(getApiWaffleFlagsUrl()) + .reply(200, {}); + await executeThunk(fetchWaffleFlags(), store.dispatch); }); it('should render page and page title correctly', () => { diff --git a/src/studio-home/card-item/index.tsx b/src/studio-home/card-item/index.tsx index e899c1a70f..4d456c776e 100644 --- a/src/studio-home/card-item/index.tsx +++ b/src/studio-home/card-item/index.tsx @@ -63,9 +63,11 @@ const CardItem: React.FC = ({ } = useSelector(getStudioHomeData); const waffleFlags = useSelector(getWaffleFlags); - const destinationUrl: string = waffleFlags.useNewCourseOutlinePage - ? path ?? url - : path ?? new URL(url, getConfig().STUDIO_BASE_URL).toString(); + const destinationUrl: string = path ?? ( + waffleFlags.useNewCourseOutlinePage + ? url + : new URL(url, getConfig().STUDIO_BASE_URL).toString() + ); const subtitle = isLibraries ? `${org} / ${number}` : `${org} / ${number} / ${run}`; const readOnlyItem = !(lmsLink || rerunLink || url || path); const showActions = !(readOnlyItem || isLibraries); diff --git a/src/utils.js b/src/utils.js index d2ba7e54d3..1c4f246a3a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -107,15 +107,19 @@ export const createCorrectInternalRoute = (checkPath) => { basePath = basePath.slice(0, -1); } + if (!checkPath.startsWith(basePath)) { + return `${basePath}${checkPath}`; + } + return checkPath; }; export function getPagePath(courseId, isMfePageEnabled, urlParameter) { if (isMfePageEnabled === 'true') { if (urlParameter === 'tabs') { - return createCorrectInternalRoute(`/course/${courseId}/pages-and-resources`); + return `/course/${courseId}/pages-and-resources`; } - return createCorrectInternalRoute(`/course/${courseId}/${urlParameter}`); + return `/course/${courseId}/${urlParameter}`; } return `${getConfig().STUDIO_BASE_URL}/${urlParameter}/${courseId}`; } diff --git a/src/utils.test.js b/src/utils.test.js index e8dcaef20b..d4fa59d373 100644 --- a/src/utils.test.js +++ b/src/utils.test.js @@ -1,8 +1,5 @@ -import { getConfig, getPath } from '@edx/frontend-platform'; - import { getFileSizeToClosestByte, - createCorrectInternalRoute, convertObjectToSnakeCase, deepConvertingKeysToCamelCase, deepConvertingKeysToSnakeCase, @@ -11,6 +8,7 @@ import { convertToDateFromString, convertToStringFromDate, isValidDate, + getPagePath, } from './utils'; jest.mock('@edx/frontend-platform', () => ({ @@ -53,53 +51,6 @@ describe('FilesAndUploads utils', () => { }); }); - describe('createCorrectInternalRoute', () => { - beforeEach(() => { - getConfig.mockReset(); - getPath.mockReset(); - }); - - it('returns the correct internal route when checkPath is not prefixed with basePath', () => { - getConfig.mockReturnValue({ PUBLIC_PATH: 'example.com' }); - getPath.mockReturnValue('/'); - - const checkPath = '/some/path'; - const result = createCorrectInternalRoute(checkPath); - - expect(result).toBe('/some/path'); - }); - - it('returns the input checkPath when it is already prefixed with basePath', () => { - getConfig.mockReturnValue({ PUBLIC_PATH: 'example.com' }); - getPath.mockReturnValue('/course-authoring'); - - const checkPath = '/course-authoring/some/path'; - const result = createCorrectInternalRoute(checkPath); - - expect(result).toBe('/course-authoring/some/path'); - }); - - it('handles basePath ending with a slash correctly', () => { - getConfig.mockReturnValue({ PUBLIC_PATH: 'example.com/' }); - getPath.mockReturnValue('/course-authoring/'); - - const checkPath = '/course-authoring/some/path'; - const result = createCorrectInternalRoute(checkPath); - - expect(result).toBe('/course-authoring/some/path'); - }); - - it('returns checkPath as is when basePath is part of checkPath', () => { - getConfig.mockReturnValue({ PUBLIC_PATH: 'example.com' }); - getPath.mockReturnValue('/example-base/'); - - const checkPath = '/example-base/some/path'; - const result = createCorrectInternalRoute(checkPath); - - expect(result).toBe(checkPath); - }); - }); - describe('convertObjectToSnakeCase', () => { it('converts object keys to snake_case', () => { const input = { firstName: 'John', lastName: 'Doe' }; @@ -205,3 +156,23 @@ describe('FilesAndUploads utils', () => { }); }); }); + +describe('getPagePath', () => { + it('returns MFE path when isMfePageEnabled is true and urlParameter is "tabs"', () => { + const courseId = '12345'; + const isMfePageEnabled = 'true'; + const urlParameter = 'tabs'; + + const result = getPagePath(courseId, isMfePageEnabled, urlParameter); + expect(result).toBe(`/course/${courseId}/pages-and-resources`); + }); + + it('returns MFE path when isMfePageEnabled is true and urlParameter is not "tabs"', () => { + const courseId = '12345'; + const isMfePageEnabled = 'true'; + const urlParameter = 'other-page'; + + const result = getPagePath(courseId, isMfePageEnabled, urlParameter); + expect(result).toBe(`/course/${courseId}/${urlParameter}`); + }); +});