From f9ef00e29f27881c44ee6ee4f36c667e26d58e56 Mon Sep 17 00:00:00 2001 From: Peter Kulko <93188219+PKulkoRaccoonGang@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:19:23 +0200 Subject: [PATCH] feat: [FC-0070] Remove backend redirects (use SPA functionality) (#1372) Introduces the ability to utilize SPA functionality when the relevant waffle flags are enabled for current MFE pages. When any new MFE page is loaded, a request is made to retrieve the waffle flags. This includes both global waffle flags related to MFE Authoring pages, as well as waffle flags specific to the current course. --- package-lock.json | 11 +- package.json | 2 +- src/CourseAuthoringPage.jsx | 3 +- src/CourseAuthoringPage.test.jsx | 9 +- src/CourseAuthoringRoutes.test.jsx | 13 +- .../ChecklistSection/ChecklistItemBody.jsx | 125 ++++++++------ .../ChecklistSection/ChecklistItemComment.jsx | 19 ++- .../ChecklistSection/ChecklistSection.jsx | 18 +-- .../ChecklistSection.test.jsx | 152 +++++++----------- src/course-checklist/CourseChecklist.jsx | 6 +- src/course-checklist/utils.js | 12 -- src/course-outline/hooks.jsx | 6 +- src/course-outline/status-bar/StatusBar.jsx | 21 +-- src/course-unit/breadcrumbs/Breadcrumbs.jsx | 14 +- .../breadcrumbs/Breadcrumbs.test.jsx | 87 +++++++--- src/custom-pages/CustomPages.jsx | 16 +- src/custom-pages/CustomPages.test.jsx | 11 ++ src/data/api.js | 17 +- src/data/selectors.js | 2 + src/data/slice.js | 22 +++ src/data/thunks.js | 14 +- src/generic/help-sidebar/HelpSidebar.jsx | 24 ++- src/generic/help-sidebar/HelpSidebarLink.jsx | 18 ++- .../help-sidebar/HelpSidebarLink.test.jsx | 46 ++++++ .../grading-sidebar/GradingSidebar.test.jsx | 39 ++--- .../ExperimentConfigurationsSection.test.jsx | 8 + .../index.jsx | 4 +- src/group-configurations/index.jsx | 1 + src/header/Header.tsx | 16 +- src/header/hooks.js | 35 ++-- src/hooks.test.ts | 119 ++++++++++++++ src/hooks.ts | 6 +- src/pages-and-resources/PagesAndResources.jsx | 2 +- src/pages-and-resources/pages/PageCard.jsx | 5 +- .../pages/PageCard.test.jsx | 60 +++++-- src/pages-and-resources/pages/PageGrid.jsx | 6 +- .../pages/PageSettingButton.jsx | 44 ++++- .../pages/PageSettingButton.test.jsx | 93 +++++++++++ src/studio-home/StudioHome.test.jsx | 10 ++ src/studio-home/card-item/CardItem.test.tsx | 2 +- src/studio-home/card-item/index.tsx | 14 +- src/studio-home/hooks.jsx | 2 + .../VerifyEmailLayout.test.jsx | 34 +++- src/textbooks/Textbooks.jsx | 11 +- src/textbooks/hooks.jsx | 8 +- src/utils.js | 4 +- src/utils.test.js | 148 ++++++++++++++--- 47 files changed, 983 insertions(+), 356 deletions(-) delete mode 100644 src/course-checklist/utils.js create mode 100644 src/data/selectors.js create mode 100644 src/generic/help-sidebar/HelpSidebarLink.test.jsx create mode 100644 src/hooks.test.ts create mode 100644 src/pages-and-resources/pages/PageSettingButton.test.jsx diff --git a/package-lock.json b/package-lock.json index 6673f5b8ce..6de8600f3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@edx/brand": "npm:@openedx/brand-openedx@^1.2.3", "@edx/browserslist-config": "1.2.0", "@edx/frontend-component-footer": "^14.1.0", - "@edx/frontend-component-header": "^5.6.0", + "@edx/frontend-component-header": "^5.7.0", "@edx/frontend-enterprise-hotjar": "^2.0.0", "@edx/frontend-platform": "^8.0.3", "@edx/openedx-atlas": "^0.6.0", @@ -2176,9 +2176,9 @@ } }, "node_modules/@edx/frontend-component-header": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-5.6.0.tgz", - "integrity": "sha512-ITLLrej6BbWVc/0baMkKg/ACTvUGSR188Rn/BC2Y82Tdu8gRsZB6+0GUsDX/6FJjeIazLXdUusKlfwVU90sXLA==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-5.7.0.tgz", + "integrity": "sha512-a7ErsU0m6yaU8VGLN4JYC1Y43W5L/zSCZSmpDZw534LiPK43k4eyU8u5Ep1yxp+8sOvB49qED4huIq+1oVDJsA==", "dependencies": { "@fortawesome/fontawesome-svg-core": "6.6.0", "@fortawesome/free-brands-svg-icons": "6.6.0", @@ -2198,7 +2198,8 @@ "@openedx/paragon": ">= 21.5.7 < 23.0.0", "prop-types": "^15.5.10", "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0" + "react-dom": "^16.9.0 || ^17.0.0", + "react-router-dom": "^6.14.2" } }, "node_modules/@edx/frontend-component-header/node_modules/react-responsive": { diff --git a/package.json b/package.json index 57ac9dfca0..0db2339a44 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@edx/brand": "npm:@openedx/brand-openedx@^1.2.3", "@edx/browserslist-config": "1.2.0", "@edx/frontend-component-footer": "^14.1.0", - "@edx/frontend-component-header": "^5.6.0", + "@edx/frontend-component-header": "^5.7.0", "@edx/frontend-enterprise-hotjar": "^2.0.0", "@edx/frontend-platform": "^8.0.3", "@edx/openedx-atlas": "^0.6.0", diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx index 41da0bc232..6b8e968277 100644 --- a/src/CourseAuthoringPage.jsx +++ b/src/CourseAuthoringPage.jsx @@ -7,7 +7,7 @@ import { } from 'react-router-dom'; import { StudioFooter } from '@edx/frontend-component-footer'; import Header from './header'; -import { fetchCourseDetail } from './data/thunks'; +import { fetchCourseDetail, fetchWaffleFlags } from './data/thunks'; import { useModel } from './generic/model-store'; import NotFoundAlert from './generic/NotFoundAlert'; import PermissionDeniedAlert from './generic/PermissionDeniedAlert'; @@ -21,6 +21,7 @@ const CourseAuthoringPage = ({ courseId, children }) => { useEffect(() => { dispatch(fetchCourseDetail(courseId)); + dispatch(fetchWaffleFlags(courseId)); }, [courseId]); useEffect(() => { 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-checklist/ChecklistSection/ChecklistItemBody.jsx b/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx index f48bdc69b3..703dd8c414 100644 --- a/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistItemBody.jsx @@ -1,70 +1,95 @@ -import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; -import { - ActionRow, - Button, - Hyperlink, - Icon, -} from '@openedx/paragon'; +import { Link } from 'react-router-dom'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { ActionRow, Button, Icon } from '@openedx/paragon'; +import { useSelector } from 'react-redux'; import { CheckCircle, RadioButtonUnchecked } from '@openedx/paragon/icons'; +import { getConfig } from '@edx/frontend-platform'; + +import { getWaffleFlags } from '../../data/selectors'; import messages from './messages'; +const getUpdateLinks = (courseId, waffleFlags) => { + const baseUrl = getConfig().STUDIO_BASE_URL; + const isLegacyGradingUrl = !waffleFlags.useNewGradingPage; + const isLegacyCertificateUrl = !waffleFlags.useNewCertificatesPage; + const isLegacyCourseDatesUrl = !waffleFlags.useNewScheduleDetailsPage; + const isLegacyOutlineUrl = !waffleFlags.useNewCourseOutlinePage; + + return { + welcomeMessage: `/course/${courseId}/course_info`, + gradingPolicy: isLegacyGradingUrl + ? `${baseUrl}/settings/grading/${courseId}` : `/course/${courseId}/settings/grading`, + certificate: isLegacyCertificateUrl + ? `${baseUrl}/certificates/${courseId}` : `/course/${courseId}/certificates`, + courseDates: isLegacyCourseDatesUrl + ? `${baseUrl}/settings/details/${courseId}#schedule` : `/course/${courseId}/settings/details/#schedule`, + proctoringEmail: `${baseUrl}/pages-and-resources/proctoring/settings`, + outline: isLegacyOutlineUrl ? `${baseUrl}/course/${courseId}` : `/course/${courseId}`, + }; +}; + const ChecklistItemBody = ({ + courseId, checkId, isCompleted, - updateLink, - // injected - intl, -}) => ( - -
- {isCompleted ? ( - - ) : ( - - )} -
-
-
- +}) => { + const intl = useIntl(); + const waffleFlags = useSelector(getWaffleFlags); + const updateLinks = getUpdateLinks(courseId, waffleFlags); + + return ( + +
+ {isCompleted ? ( + + ) : ( + + )}
-
- +
+
+ +
+
+ +
-
- - {updateLink && ( - - - - )} -
-); + + {updateLinks?.[checkId] && ( + + + + )} + + ); +}; ChecklistItemBody.defaultProps = { updateLink: null, }; ChecklistItemBody.propTypes = { + courseId: PropTypes.string.isRequired, checkId: PropTypes.string.isRequired, isCompleted: PropTypes.bool.isRequired, updateLink: PropTypes.string, - // injected - intl: intlShape.isRequired, }; -export default injectIntl(ChecklistItemBody); +export default ChecklistItemBody; diff --git a/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx b/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx index 92fb83ea32..deec84f2c3 100644 --- a/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistItemComment.jsx @@ -1,15 +1,23 @@ -import React from 'react'; import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; import { injectIntl, FormattedMessage, FormattedNumber } from '@edx/frontend-platform/i18n'; -import { Hyperlink, Icon } from '@openedx/paragon'; +import { Icon } from '@openedx/paragon'; +import { Link } from 'react-router-dom'; import { ModeComment } from '@openedx/paragon/icons'; +import { getConfig } from '@edx/frontend-platform'; +import { getWaffleFlags } from '../../data/selectors'; import messages from './messages'; const ChecklistItemComment = ({ + courseId, checkId, - outlineUrl, data, }) => { + const waffleFlags = useSelector(getWaffleFlags); + + const getPathToCourseOutlinePage = (assignmentId) => (waffleFlags.useNewCourseOutlinePage + ? `/course/${courseId}#${assignmentId}` : `${getConfig().STUDIO_BASE_URL}/course/${courseId}#${assignmentId}`); + const commentWrapper = (comment) => (
@@ -79,9 +87,9 @@ const ChecklistItemComment = ({
    {gradedAssignmentsOutsideDateRange.map(assignment => (
  • - + {assignment.displayName} - +
  • ))}
@@ -96,6 +104,7 @@ const ChecklistItemComment = ({ }; ChecklistItemComment.propTypes = { + courseId: PropTypes.string.isRequired, checkId: PropTypes.string.isRequired, outlineUrl: PropTypes.string.isRequired, data: PropTypes.oneOfType([ diff --git a/src/course-checklist/ChecklistSection/ChecklistSection.jsx b/src/course-checklist/ChecklistSection/ChecklistSection.jsx index 46fe71889c..3b11b6e05b 100644 --- a/src/course-checklist/ChecklistSection/ChecklistSection.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistSection.jsx @@ -10,11 +10,11 @@ import ChecklistItemComment from './ChecklistItemComment'; import { checklistItems } from './utils/courseChecklistData'; const ChecklistSection = ({ + courseId, dataHeading, data, idPrefix, isLoading, - updateLinks, }) => { const dataList = checklistItems[idPrefix]; const getCompletionCountID = () => (`${idPrefix}-completion-count`); @@ -37,8 +37,6 @@ const ChecklistSection = ({ {checks.map(check => { const checkId = check.id; const isCompleted = values[checkId]; - const updateLink = updateLinks?.[checkId]; - const outlineUrl = updateLinks.outline; return (
- +
- +
); @@ -61,11 +59,11 @@ const ChecklistSection = ({ }; ChecklistSection.defaultProps = { - updateLinks: {}, data: {}, }; ChecklistSection.propTypes = { + courseId: PropTypes.string.isRequired, dataHeading: PropTypes.string.isRequired, data: PropTypes.oneOfType([ PropTypes.shape({ @@ -129,14 +127,6 @@ ChecklistSection.propTypes = { ]), idPrefix: PropTypes.string.isRequired, isLoading: PropTypes.bool.isRequired, - updateLinks: PropTypes.shape({ - welcomeMessage: PropTypes.string, - gradingPolicy: PropTypes.string, - certificate: PropTypes.string, - courseDates: PropTypes.string, - proctoringEmail: PropTypes.string, - outline: PropTypes.string, - }), }; export default injectIntl(ChecklistSection); diff --git a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx index 1c8317c903..c4ad7f3262 100644 --- a/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx +++ b/src/course-checklist/ChecklistSection/ChecklistSection.test.jsx @@ -1,59 +1,49 @@ -/* eslint-disable */ -import { - render, - within, - screen, -} from '@testing-library/react'; +import { within, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { camelCaseObject } from '@edx/frontend-platform'; -import initializeStore from '../../store'; -import { initialState,generateCourseLaunchData } from '../factories/mockApiResponses'; -import messages from './messages'; -import ChecklistSection from './index'; +import { initializeMocks, render } from '../../testUtils'; +import { getApiWaffleFlagsUrl } from '../../data/api'; +import { fetchWaffleFlags } from '../../data/thunks'; +import { generateCourseLaunchData } from '../factories/mockApiResponses'; +import { executeThunk } from '../../utils'; import { checklistItems } from './utils/courseChecklistData'; -import getUpdateLinks from '../utils'; +import messages from './messages'; + +import ChecklistSection from '.'; const testData = camelCaseObject(generateCourseLaunchData()); +const courseId = '123'; const defaultProps = { + courseId, data: testData, dataHeading: 'Test checklist', idPrefix: 'launchChecklist', - updateLinks: getUpdateLinks('courseId'), isLoading: false, }; const testChecklistData = checklistItems[defaultProps.idPrefix]; -const completedItemIds = ['welcomeMessage', 'courseDates'] +const completedItemIds = ['welcomeMessage', 'courseDates']; const renderComponent = (props) => { - render( - - - - - , - ); + render(); }; -let store; - describe('ChecklistSection', () => { - beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore(initialState); + beforeEach(async () => { + const { axiosMock, reduxStore } = initializeMocks(); + axiosMock + .onGet(getApiWaffleFlagsUrl(courseId)) + .reply(200, { + useNewGradingPage: true, + useNewCertificatesPage: true, + useNewScheduleDetailsPage: true, + useNewCourseOutlinePage: true, + }); + await executeThunk(fetchWaffleFlags(courseId), reduxStore.dispatch); }); it('a heading using the dataHeading prop', () => { @@ -64,6 +54,7 @@ describe('ChecklistSection', () => { it('completion count text', () => { renderComponent(defaultProps); + const completionText = `${completedItemIds.length}/6 completed`; expect(screen.getByTestId('completion-subheader').textContent).toEqual(completionText); }); @@ -122,7 +113,7 @@ describe('ChecklistSection', () => { grades: { ...defaultProps.data.grades, sumOfWeights: 1, - } + }, }, }; renderComponent(props); @@ -154,7 +145,7 @@ describe('ChecklistSection', () => { ...defaultProps.data.assignments, assignmentsWithDatesAfterEnd: [], assignmentsWithOraDatesBeforeStart: [], - } + }, }, }; renderComponent(props); @@ -183,73 +174,52 @@ describe('ChecklistSection', () => { expect(assigmentLinks[1].textContent).toEqual('ORA subsection'); }); }); -}); -testChecklistData.forEach((check) => { - describe(`check with id '${check.id}'`, () => { - let checkItem; + describe('Checklist Component', () => { + let checklistData; + let updateLinks; + beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore(initialState); renderComponent(defaultProps); - checkItem = screen.getAllByTestId(`checklist-item-${check.id}`); - }); - it('renders', () => { - expect(checkItem).toHaveLength(1); - }); - - it('has correct icon', () => { - const icon = screen.getAllByTestId(`icon-${check.id}`) - - expect(icon).toHaveLength(1); + checklistData = testChecklistData.map((item) => ({ + itemId: item.id, + checklistItem: screen.getAllByTestId(`checklist-item-${item.id}`), + icon: screen.getAllByTestId(`icon-${item.id}`), + shortDescription: messages[`${item.id}ShortDescription`].defaultMessage, + longDescription: messages[`${item.id}LongDescription`].defaultMessage, + })); - const { queryByTestId } = within(icon[0]); - if (completedItemIds.includes(check.id)) { - expect(queryByTestId('completed-icon')).not.toBeNull(); - } else { - expect(queryByTestId('uncompleted-icon')).not.toBeNull(); - } + updateLinks = screen.getAllByTestId('update-link'); }); - it('has correct short description', () => { - const { getByText } = within(checkItem[0]); - const shortDescription = messages[`${check.id}ShortDescription`].defaultMessage; - expect(getByText(shortDescription)).toBeVisible(); - }); + it('should display the correct icons based on completion status', () => { + checklistData.forEach(({ itemId, icon }) => { + const { queryByTestId } = within(icon[0]); - it('has correct long description', () => { - const { getByText } = within(checkItem[0]); - const longDescription = messages[`${check.id}LongDescription`].defaultMessage; - expect(getByText(longDescription)).toBeVisible(); + if (completedItemIds.includes(itemId)) { + expect(queryByTestId('completed-icon')).not.toBeNull(); + } else { + expect(queryByTestId('uncompleted-icon')).not.toBeNull(); + } + }); }); - describe('has correct link', () => { - const links = getUpdateLinks('courseId') - const shouldShowLink = Object.keys(links).includes(check.id); - - if (shouldShowLink) { - it('with a Hyperlink', () => { - const { getByRole, getByText } = within(checkItem[0]); + it('should display short and long descriptions for each checklist item', () => { + checklistData.forEach(({ checklistItem, shortDescription, longDescription }) => { + const { getByText } = within(checklistItem[0]); - expect(getByText('Update')).toBeVisible(); - - expect(getByRole('link').href).toMatch(links[check.id]); - }); - } else { - it('without a Hyperlink', () => { - const { queryByText } = within(checkItem[0]); + expect(getByText(shortDescription)).toBeVisible(); + expect(getByText(longDescription)).toBeVisible(); + }); + }); - expect(queryByText('Update')).toBeNull(); + it('should have valid update links for each checklist item', () => { + checklistData.forEach(({ itemId }) => { + updateLinks.forEach((link) => { + expect(link).toHaveAttribute('href', updateLinks[itemId]); }); - } + }); }); }); }); diff --git a/src/course-checklist/CourseChecklist.jsx b/src/course-checklist/CourseChecklist.jsx index 5766bfe45e..8c15a11dc0 100644 --- a/src/course-checklist/CourseChecklist.jsx +++ b/src/course-checklist/CourseChecklist.jsx @@ -13,7 +13,6 @@ import AriaLiveRegion from './AriaLiveRegion'; import { RequestStatus } from '../data/constants'; import ChecklistSection from './ChecklistSection'; import { fetchCourseLaunchQuery, fetchCourseBestPracticesQuery } from './data/thunks'; -import getUpdateLinks from './utils'; const CourseChecklist = ({ courseId, @@ -23,7 +22,6 @@ const CourseChecklist = ({ const dispatch = useDispatch(); const courseDetails = useModel('courseDetails', courseId); const enableQuality = getConfig().ENABLE_CHECKLIST_QUALITY === 'true'; - const updateLinks = getUpdateLinks(courseId); useEffect(() => { dispatch(fetchCourseLaunchQuery({ courseId })); @@ -66,19 +64,19 @@ const CourseChecklist = ({ /> {enableQuality && ( )} diff --git a/src/course-checklist/utils.js b/src/course-checklist/utils.js deleted file mode 100644 index 3e6b549ff0..0000000000 --- a/src/course-checklist/utils.js +++ /dev/null @@ -1,12 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; - -const getUpdateLinks = (courseId) => ({ - welcomeMessage: `${getConfig().STUDIO_BASE_URL}/course_info/${courseId}`, - gradingPolicy: `${getConfig().STUDIO_BASE_URL}/settings/grading/${courseId}`, - certificate: `${getConfig().STUDIO_BASE_URL}/certificates/${courseId}`, - courseDates: `${getConfig().STUDIO_BASE_URL}/settings/details/${courseId}#schedule`, - proctoringEmail: 'pages-and-resources/proctoring/settings', - outline: `${getConfig().STUDIO_BASE_URL}/course/${courseId}`, -}); - -export default getUpdateLinks; diff --git a/src/course-outline/hooks.jsx b/src/course-outline/hooks.jsx index 25b9d8bedd..739d599432 100644 --- a/src/course-outline/hooks.jsx +++ b/src/course-outline/hooks.jsx @@ -6,6 +6,7 @@ import { getConfig } from '@edx/frontend-platform'; import { copyToClipboard } from '../generic/data/thunks'; import { getSavingStatus as getGenericSavingStatus } from '../generic/data/selectors'; +import { getWaffleFlags } from '../data/selectors'; import { RequestStatus } from '../data/constants'; import { COURSE_BLOCK_NAMES } from './constants'; import { @@ -58,6 +59,7 @@ import { const useCourseOutline = ({ courseId }) => { const dispatch = useDispatch(); const navigate = useNavigate(); + const waffleFlags = useSelector(getWaffleFlags); const { reindexLink, @@ -112,7 +114,7 @@ const useCourseOutline = ({ courseId }) => { }; const getUnitUrl = (locator) => { - if (getConfig().ENABLE_UNIT_PAGE === 'true') { + if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) { return `/course/${courseId}/container/${locator}`; } return `${getConfig().STUDIO_BASE_URL}/container/${locator}`; @@ -120,7 +122,7 @@ const useCourseOutline = ({ courseId }) => { const openUnitPage = (locator) => { const url = getUnitUrl(locator); - if (getConfig().ENABLE_UNIT_PAGE === 'true') { + if (getConfig().ENABLE_UNIT_PAGE === 'true' && waffleFlags.useNewUnitPage) { navigate(url); } else { window.location.assign(url); diff --git a/src/course-outline/status-bar/StatusBar.jsx b/src/course-outline/status-bar/StatusBar.jsx index ed8fa28309..b4e25fd357 100644 --- a/src/course-outline/status-bar/StatusBar.jsx +++ b/src/course-outline/status-bar/StatusBar.jsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import { useContext } from 'react'; import moment from 'moment/moment'; import PropTypes from 'prop-types'; import { FormattedDate, useIntl } from '@edx/frontend-platform/i18n'; @@ -6,11 +6,14 @@ import { getConfig } from '@edx/frontend-platform/config'; import { Button, Hyperlink, Form, Stack, useToggle, } from '@openedx/paragon'; +import { Link } from 'react-router-dom'; import { AppContext } from '@edx/frontend-platform/react'; +import { useSelector } from 'react-redux'; import { ContentTagsDrawerSheet } from '../../content-tags-drawer'; import TagCount from '../../generic/tag-count'; import { useHelpUrls } from '../../help-urls/hooks'; +import { getWaffleFlags } from '../../data/selectors'; import { VIDEO_SHARING_OPTIONS } from '../constants'; import { useContentTagsCount } from '../../generic/data/apiHooks'; import messages from './messages'; @@ -43,6 +46,7 @@ const StatusBar = ({ }) => { const intl = useIntl(); const { config } = useContext(AppContext); + const waffleFlags = useSelector(getWaffleFlags); const { courseReleaseDate, @@ -62,7 +66,6 @@ const StatusBar = ({ const courseReleaseDateObj = moment.utc(courseReleaseDate, 'MMM DD, YYYY at HH:mm UTC', true); const checkListTitle = `${completedCourseLaunchChecks + completedCourseBestPracticesChecks}/${totalCourseLaunchChecks + totalCourseBestPracticesChecks}`; - const checklistDestination = () => new URL(`checklists/${courseId}`, config.STUDIO_BASE_URL).href; const scheduleDestination = () => new URL(`settings/details/${courseId}#schedule`, config.STUDIO_BASE_URL).href; const { @@ -82,10 +85,9 @@ const StatusBar = ({ <> - {courseReleaseDateObj.isValid() ? ( ) : courseReleaseDate} - + @@ -107,13 +109,12 @@ const StatusBar = ({ - {checkListTitle} {intl.formatMessage(messages.checklistCompleted)} - +
diff --git a/src/course-unit/breadcrumbs/Breadcrumbs.jsx b/src/course-unit/breadcrumbs/Breadcrumbs.jsx index 8dd34cfc52..26bfa53562 100644 --- a/src/course-unit/breadcrumbs/Breadcrumbs.jsx +++ b/src/course-unit/breadcrumbs/Breadcrumbs.jsx @@ -1,12 +1,14 @@ import { useSelector } from 'react-redux'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Dropdown, Icon } from '@openedx/paragon'; +import { Link } from 'react-router-dom'; import { ArrowDropDown as ArrowDropDownIcon, ChevronRight as ChevronRightIcon, } from '@openedx/paragon/icons'; +import { getConfig } from '@edx/frontend-platform'; -import { createCorrectInternalRoute } from '../../utils'; +import { getWaffleFlags } from '../../data/selectors'; import { getCourseSectionVertical } from '../data/selectors'; import messages from './messages'; @@ -14,6 +16,10 @@ const Breadcrumbs = () => { const intl = useIntl(); const { ancestorXblocks } = useSelector(getCourseSectionVertical); const [section, subsection] = ancestorXblocks ?? []; + const waffleFlags = useSelector(getWaffleFlags); + + const getPathToCourseOutlinePage = (url) => (waffleFlags.useNewCourseOutlinePage + ? url : `${getConfig().STUDIO_BASE_URL}${url}`); return (