diff --git a/example.env.config.jsx b/example.env.config.jsx index ecbb4ba95f..987568a1a2 100644 --- a/example.env.config.jsx +++ b/example.env.config.jsx @@ -1,6 +1,11 @@ import UnitTranslationPlugin from '@edx/unit-translation-selector-plugin'; import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework'; +import { + OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID, + OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID, +} from '@src/courseware/course/sidebar/sidebars/course-outline'; + // Load environment variables from .env file const config = { ...process.env, @@ -18,6 +23,14 @@ const config = { }, ], }, + [OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID]: { + keepDefault: true, + plugins: [], + }, + [OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID]: { + keepDefault: true, + plugins: [], + }, }, }; diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap index 04c4218df9..56f8cd7ed4 100644 --- a/src/course-home/data/__snapshots__/redux.test.js.snap +++ b/src/course-home/data/__snapshots__/redux.test.js.snap @@ -16,7 +16,6 @@ Object { "courseId": null, "courseOutline": Object {}, "courseOutlineShouldUpdate": false, - "courseOutlineSidebarSettings": Object {}, "courseOutlineStatus": "loading", "courseStatus": "loading", "rightSidebarSettings": Object {}, @@ -408,7 +407,6 @@ Object { "courseId": null, "courseOutline": Object {}, "courseOutlineShouldUpdate": false, - "courseOutlineSidebarSettings": Object {}, "courseOutlineStatus": "loading", "courseStatus": "loading", "rightSidebarSettings": Object {}, @@ -681,7 +679,6 @@ Object { "courseId": null, "courseOutline": Object {}, "courseOutlineShouldUpdate": false, - "courseOutlineSidebarSettings": Object {}, "courseOutlineStatus": "loading", "courseStatus": "loading", "rightSidebarSettings": Object {}, diff --git a/src/courseware/CoursewareContainer.jsx b/src/courseware/CoursewareContainer.jsx index 472345f89c..8929419075 100644 --- a/src/courseware/CoursewareContainer.jsx +++ b/src/courseware/CoursewareContainer.jsx @@ -12,9 +12,6 @@ import { getSequenceForUnitDeprecated, saveSequencePosition, } from './data'; -import { - fetchOutlineTab, -} from '../course-home/data'; import { TabPage } from '../tab-page'; import Course from './course'; @@ -142,12 +139,6 @@ class CoursewareContainer extends Component { this.props.fetchCourse(courseId); }); - checkFetchOutlineData = memoize((courseId) => { - if (courseId) { - this.props.fetchOutlineTab(courseId); - } - }); - checkFetchSequence = memoize((sequenceId) => { if (sequenceId) { this.props.fetchSequence(sequenceId); @@ -156,14 +147,12 @@ class CoursewareContainer extends Component { componentDidMount() { const { - courseId, routeCourseId, routeSequenceId, } = this.props; // Load data whenever the course or sequence ID changes. this.checkFetchCourse(routeCourseId); this.checkFetchSequence(routeSequenceId); - this.checkFetchOutlineData(courseId); } componentDidUpdate() { @@ -185,7 +174,6 @@ class CoursewareContainer extends Component { // Load data whenever the course or sequence ID changes. this.checkFetchCourse(routeCourseId); this.checkFetchSequence(routeSequenceId); - this.checkFetchOutlineData(courseId); // Check if we should save our sequence position. Only do this when the route unit ID changes. this.checkSaveSequencePosition(routeUnitId); @@ -345,7 +333,6 @@ CoursewareContainer.propTypes = { checkBlockCompletion: PropTypes.func.isRequired, fetchCourse: PropTypes.func.isRequired, fetchSequence: PropTypes.func.isRequired, - fetchOutlineTab: PropTypes.func.isRequired, navigate: PropTypes.func.isRequired, }; @@ -468,5 +455,4 @@ export default connect(mapStateToProps, { saveSequencePosition, fetchCourse, fetchSequence, - fetchOutlineTab, })(withParamsAndNavigation(CoursewareContainer)); diff --git a/src/courseware/course/Course.jsx b/src/courseware/course/Course.jsx index 611b614ae3..f673e5ca7a 100644 --- a/src/courseware/course/Course.jsx +++ b/src/courseware/course/Course.jsx @@ -1,14 +1,18 @@ import { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Helmet } from 'react-helmet'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; import { breakpoints, useWindowSize } from '@openedx/paragon'; +import { PluginSlot } from '@openedx/frontend-plugin-framework'; -import { AlertList } from '../../generic/user-messages'; -import { useModel } from '../../generic/model-store'; -import { getCoursewareOutlineSidebarSettings } from '../data/selectors'; -import { Trigger as CourseOutlineTrigger } from './sidebar/sidebars/course-outline'; +import { AlertList } from '@src/generic/user-messages'; +import { useModel } from '@src/generic/model-store'; +import { + OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID, + Trigger as CourseOutlineTrigger, + checkIsOutlineSidebarAvailable, +} from './sidebar/sidebars/course-outline'; import Chat from './chat/Chat'; import SidebarProvider from './sidebar/SidebarContextProvider'; import SidebarTriggers from './sidebar/SidebarTriggers'; @@ -36,8 +40,7 @@ const Course = ({ const sequence = useModel('sequences', sequenceId); const section = useModel('sections', sequence ? sequence.sectionId : null); const enableNewSidebar = getConfig().ENABLE_NEW_SIDEBAR; - const { enabled: isEnabledOutlineSidebar = false } = useSelector(getCoursewareOutlineSidebarSettings); - const navigationDisabled = isEnabledOutlineSidebar || (sequence?.navigationDisabled ?? false); + const navigationDisabled = checkIsOutlineSidebarAvailable() || (sequence?.navigationDisabled ?? false); const pageTitleBreadCrumbs = [ sequence, @@ -100,7 +103,9 @@ const Course = ({ )}
- + + + {enableNewSidebar === 'true' ? : }
diff --git a/src/courseware/course/Course.test.jsx b/src/courseware/course/Course.test.jsx index 2e51a53ff0..bbff8e4e83 100644 --- a/src/courseware/course/Course.test.jsx +++ b/src/courseware/course/Course.test.jsx @@ -59,7 +59,7 @@ describe('Course', () => { it('loads learning sequence', async () => { render(, { wrapWithRouter: true }); - expect(screen.queryByRole('navigation', { name: 'breadcrumb' })).not.toBeInTheDocument(); + expect(screen.queryByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument(); expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument(); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); @@ -210,9 +210,7 @@ describe('Course', () => { { type: 'vertical' }, { courseId: courseMetadata.id }, )); - const testStore = await initializeTestStore({ - courseMetadata, unitBlocks, outlineSidebarSettings: { enabled: false }, - }, false); + const testStore = await initializeTestStore({ courseMetadata, unitBlocks }, false); const { courseware, models } = testStore.getState(); const { courseId, sequenceId } = courseware; const testData = { diff --git a/src/courseware/course/sequence/Sequence.jsx b/src/courseware/course/sequence/Sequence.jsx index c08e0f0542..cc79f2ab0d 100644 --- a/src/courseware/course/sequence/Sequence.jsx +++ b/src/courseware/course/sequence/Sequence.jsx @@ -12,15 +12,18 @@ import { useSelector } from 'react-redux'; import SequenceExamWrapper from '@edx/frontend-lib-special-exams'; import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import { useModel } from '@src/generic/model-store'; +import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '@src/alerts/sequence-alerts/hooks'; import PageLoading from '../../../generic/PageLoading'; -import { useModel } from '../../../generic/model-store'; -import { useSequenceBannerTextAlert, useSequenceEntranceExamAlert } from '../../../alerts/sequence-alerts/hooks'; import CourseLicense from '../course-license'; import Sidebar from '../sidebar/Sidebar'; import NewSidebar from '../new-sidebar/Sidebar'; import { LAYOUT_RIGHT, LAYOUT_LEFT } from '../sidebar/common/constants'; -import { Trigger as CourseOutlineTrigger } from '../sidebar/sidebars/course-outline'; +import { + OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID, + Trigger as CourseOutlineTrigger, +} from '../sidebar/sidebars/course-outline'; import messages from './messages'; import HiddenAfterDue from './hidden-after-due'; import { SequenceNavigation, UnitNavigation } from './sequence-navigation'; @@ -146,7 +149,9 @@ const Sequence = ({ const defaultContent = ( <>
- + + +
diff --git a/src/courseware/course/sequence/Sequence.test.jsx b/src/courseware/course/sequence/Sequence.test.jsx index 25a8de36b4..c6d6ab9a82 100644 --- a/src/courseware/course/sequence/Sequence.test.jsx +++ b/src/courseware/course/sequence/Sequence.test.jsx @@ -82,8 +82,8 @@ describe('Sequence', () => { ); await waitFor(() => expect(screen.queryByText('Loading locked content messaging...')).toBeInTheDocument()); - // `Previous`, `Prerequisite` and `Close Tray` buttons. - expect(screen.getAllByRole('button').length).toEqual(3); + // `Previous` and `Bookmark` buttons. + expect(screen.getAllByRole('button').length).toEqual(2); // `Active` and `Next` buttons. expect(screen.getAllByRole('link').length).toEqual(2); @@ -136,8 +136,8 @@ describe('Sequence', () => { it('handles loading unit', async () => { render(, { wrapWithRouter: true }); expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument(); - // `Previous`, `Prerequisite` and `Close Tray` buttons. - expect(screen.getAllByRole('button')).toHaveLength(3); + // `Previous` and `Bookmark` buttons. + expect(screen.getAllByRole('button')).toHaveLength(2); // Renders `Next` button plus one button for each unit. expect(screen.getAllByRole('link')).toHaveLength(1 + unitBlocks.length); diff --git a/src/courseware/course/sidebar/Sidebar.jsx b/src/courseware/course/sidebar/Sidebar.jsx index 661e98793c..90742050e7 100644 --- a/src/courseware/course/sidebar/Sidebar.jsx +++ b/src/courseware/course/sidebar/Sidebar.jsx @@ -11,7 +11,7 @@ const Sidebar = ({ layout }) => { return null; } - if (layout !== SIDEBARS[currentSidebar].LAYOUT) { + if (layout !== SIDEBARS[currentSidebar]?.LAYOUT) { return null; } diff --git a/src/courseware/course/sidebar/SidebarContextProvider.jsx b/src/courseware/course/sidebar/SidebarContextProvider.jsx index aee5e02b4f..34c630680b 100644 --- a/src/courseware/course/sidebar/SidebarContextProvider.jsx +++ b/src/courseware/course/sidebar/SidebarContextProvider.jsx @@ -9,7 +9,11 @@ import { useModel } from '@src/generic/model-store'; import { getLocalStorage, setLocalStorage } from '@src/data/localStorage'; import { getRightSidebarSettings } from '../../data/selectors'; -import * as courseOutlineSidebar from './sidebars/course-outline'; +import { + ID as courseOutlineSidebarId, + OUTLINE_SIDEBAR_SESSION_STORAGE_NAME, + checkIsOutlineSidebarAvailable, +} from './sidebars/course-outline'; import * as discussionsSidebar from './sidebars/discussions'; import * as notificationsSidebar from './sidebars/notifications'; import SidebarContext from './SidebarContext'; @@ -27,7 +31,7 @@ const SidebarProvider = ({ const shouldDisplaySidebarOpen = useWindowSize().width > breakpoints.extraLarge.minWidth; const query = new URLSearchParams(window.location.search); const isDefaultDisplayRightSidebar = useSelector(getRightSidebarSettings).enabled; - const isCollapsedOutlineSidebar = window.sessionStorage.getItem('hideCourseOutlineSidebar'); + const isCollapsedOutlineSidebar = window.sessionStorage.getItem(OUTLINE_SIDEBAR_SESSION_STORAGE_NAME); const isInitiallySidebarOpen = shouldDisplaySidebarOpen || query.get('sidebar') === 'true'; let initialSidebar = null; @@ -37,7 +41,8 @@ const SidebarProvider = ({ ? SIDEBARS[discussionsSidebar.ID].ID : verifiedMode && SIDEBARS[notificationsSidebar.ID].ID; } else { - initialSidebar = !isCollapsedOutlineSidebar && SIDEBARS[courseOutlineSidebar.ID].ID; + initialSidebar = checkIsOutlineSidebarAvailable() + && !isCollapsedOutlineSidebar && SIDEBARS[courseOutlineSidebarId].ID; } } const [currentSidebar, setCurrentSidebar] = useState(initialSidebar); diff --git a/src/courseware/course/sidebar/SidebarTriggers.jsx b/src/courseware/course/sidebar/SidebarTriggers.jsx index d1a48a5d41..63679b87a3 100644 --- a/src/courseware/course/sidebar/SidebarTriggers.jsx +++ b/src/courseware/course/sidebar/SidebarTriggers.jsx @@ -20,7 +20,7 @@ const SidebarTriggers = () => { return (
toggleSidebar(sidebarId)} key={sidebarId} /> diff --git a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.jsx b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.jsx index eeee7c0b09..e6779354d8 100644 --- a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.jsx +++ b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.jsx @@ -8,14 +8,13 @@ import { ChevronLeft as ChevronLeftIcon, } from '@openedx/paragon/icons'; -import { useModel } from '../../../../../generic/model-store'; -import { LOADING, LOADED } from '../../../../../course-home/data/slice'; -import PageLoading from '../../../../../generic/PageLoading'; +import { useModel } from '@src/generic/model-store'; +import PageLoading from '@src/generic/PageLoading'; +import { LOADING, LOADED } from '@src/course-home/data/slice'; import { getSequenceId, getCourseOutline, getCourseOutlineStatus, - getCoursewareOutlineSidebarSettings, getCourseOutlineShouldUpdate, } from '../../../../data/selectors'; import { getCourseOutlineStructure } from '../../../../data/thunks'; @@ -23,6 +22,7 @@ import SidebarContext from '../../SidebarContext'; import { ID } from './CourseOutlineTrigger'; import SidebarSection from './SidebarSection'; import SidebarSequence from './SidebarSequence'; +import { OUTLINE_SIDEBAR_SESSION_STORAGE_NAME } from './constants'; import messages from './messages'; const CourseOutlineTray = ({ intl }) => { @@ -34,7 +34,6 @@ const CourseOutlineTray = ({ intl }) => { const { sections = {}, sequences = {} } = useSelector(getCourseOutline); const courseOutlineStatus = useSelector(getCourseOutlineStatus); const courseOutlineShouldUpdate = useSelector(getCourseOutlineShouldUpdate); - const { enabled: isEnabled } = useSelector(getCoursewareOutlineSidebarSettings); const { courseId, @@ -61,10 +60,10 @@ const CourseOutlineTray = ({ intl }) => { const handleToggleCollapse = () => { if (currentSidebar === ID) { toggleSidebar(null); - window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true'); + window.sessionStorage.setItem(OUTLINE_SIDEBAR_SESSION_STORAGE_NAME, 'true'); } else { toggleSidebar(ID); - window.sessionStorage.removeItem('hideCourseOutlineSidebar'); + window.sessionStorage.removeItem(OUTLINE_SIDEBAR_SESSION_STORAGE_NAME); } }; @@ -104,12 +103,12 @@ const CourseOutlineTray = ({ intl }) => { ); useEffect(() => { - if ((isEnabled && courseOutlineStatus !== LOADED) || courseOutlineShouldUpdate) { + if ((courseOutlineStatus !== LOADED) || courseOutlineShouldUpdate) { dispatch(getCourseOutlineStructure(courseId)); } - }, [courseId, isEnabled, courseOutlineShouldUpdate]); + }, [courseId, courseOutlineShouldUpdate]); - if (!isEnabled || isActiveEntranceExam) { + if (isActiveEntranceExam) { return null; } diff --git a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.test.jsx b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.test.jsx index b9b490d1c2..a87aeb1aa8 100644 --- a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.test.jsx +++ b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTray.test.jsx @@ -67,15 +67,6 @@ describe('', () => { expect(screen.queryByRole('button', { name: 'Course Outline' })).not.toBeInTheDocument(); }); - it('doesn\'t render when outline sidebar is disabled', async () => { - await initTestStore({ outlineSidebarSettings: { enabled: false } }); - renderWithProvider(); - - await expect(screen.queryByText(messages.loading.defaultMessage)).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: section.title })).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: messages.toggleCourseOutlineTrigger.defaultMessage })).not.toBeInTheDocument(); - }); - it('renders correctly when course outline is loaded', async () => { await initTestStore(); renderWithProvider(); diff --git a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.jsx b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.jsx index 065421ac3c..c376c1b4fd 100644 --- a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.jsx +++ b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.jsx @@ -1,15 +1,14 @@ import { useContext } from 'react'; -import { useSelector } from 'react-redux'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { IconButton } from '@openedx/paragon'; import { MenuOpen as MenuOpenIcon } from '@openedx/paragon/icons'; -import { useModel } from '../../../../../generic/model-store'; -import { getCoursewareOutlineSidebarSettings } from '../../../../data/selectors'; +import { useModel } from '@src/generic/model-store'; import { LAYOUT_LEFT } from '../../common/constants'; import SidebarContext from '../../SidebarContext'; +import { OUTLINE_SIDEBAR_SESSION_STORAGE_NAME } from './constants'; import messages from './messages'; export const ID = 'COURSE_OUTLINE'; @@ -28,22 +27,21 @@ const CourseOutlineTrigger = ({ intl, isMobileView }) => { entranceExamEnabled, entranceExamPassed, } = course.entranceExamData || {}; - const { enabled: isEnabled } = useSelector(getCoursewareOutlineSidebarSettings); const isDisplayForDesktopView = !isMobileView && !shouldDisplayFullScreen && currentSidebar !== ID; const isDisplayForMobileView = isMobileView && shouldDisplayFullScreen; const isActiveEntranceExam = entranceExamEnabled && !entranceExamPassed; - if ((!isDisplayForDesktopView && !isDisplayForMobileView) || !isEnabled || isActiveEntranceExam) { + if ((!isDisplayForDesktopView && !isDisplayForMobileView) || isActiveEntranceExam) { return null; } const handleToggleCollapse = () => { if (currentSidebar === ID) { toggleSidebar(null); - window.sessionStorage.setItem('hideCourseOutlineSidebar', 'true'); + window.sessionStorage.setItem(OUTLINE_SIDEBAR_SESSION_STORAGE_NAME, 'true'); } else { toggleSidebar(ID); - window.sessionStorage.removeItem('hideCourseOutlineSidebar'); + window.sessionStorage.removeItem(OUTLINE_SIDEBAR_SESSION_STORAGE_NAME); } }; diff --git a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.test.jsx b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.test.jsx index 25aa0901d8..8890d3a89c 100644 --- a/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.test.jsx +++ b/src/courseware/course/sidebar/sidebars/course-outline/CourseOutlineTrigger.test.jsx @@ -43,7 +43,7 @@ describe('', () => { it('renders correctly for desktop when sidebar is enabled', async () => { const mockToggleSidebar = jest.fn(); - await initTestStore({ outlineSidebarSettings: { enabled: true } }); + await initTestStore(); renderWithProvider({ toggleSidebar: mockToggleSidebar }, { isMobileView: false }); const toggleButton = await screen.getByRole('button', { @@ -59,7 +59,7 @@ describe('', () => { it('renders correctly for mobile when sidebar is enabled', async () => { const mockToggleSidebar = jest.fn(); - await initTestStore({ outlineSidebarSettings: { enabled: true } }); + await initTestStore(); renderWithProvider({ toggleSidebar: mockToggleSidebar, shouldDisplayFullScreen: true, @@ -78,7 +78,7 @@ describe('', () => { it('changes current sidebar value on click', async () => { const mockToggleSidebar = jest.fn(); - await initTestStore({ outlineSidebarSettings: { enabled: true } }); + await initTestStore(); renderWithProvider({ toggleSidebar: mockToggleSidebar, shouldDisplayFullScreen: true, @@ -95,14 +95,4 @@ describe('', () => { expect(mockToggleSidebar).toHaveBeenCalledTimes(1); expect(mockToggleSidebar).toHaveBeenCalledWith(null); }); - - it('does not render when isEnabled is false', async () => { - await initTestStore({ outlineSidebarSettings: { enabled: false } }); - renderWithProvider({}, { isMobileView: false }); - - const toggleButton = await screen.queryByRole('button', { - name: messages.toggleCourseOutlineTrigger.defaultMessage, - }); - expect(toggleButton).not.toBeInTheDocument(); - }); }); diff --git a/src/courseware/course/sidebar/sidebars/course-outline/constants.js b/src/courseware/course/sidebar/sidebars/course-outline/constants.js new file mode 100644 index 0000000000..8ad0a4bfc0 --- /dev/null +++ b/src/courseware/course/sidebar/sidebars/course-outline/constants.js @@ -0,0 +1,5 @@ +export const OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID = 'course_outline_sidebar_triggers_desktop_plugin'; + +export const OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID = 'course_outline_sidebar_triggers_mobile_plugin'; + +export const OUTLINE_SIDEBAR_SESSION_STORAGE_NAME = 'hideCourseOutlineSidebar'; diff --git a/src/courseware/course/sidebar/sidebars/course-outline/index.js b/src/courseware/course/sidebar/sidebars/course-outline/index.js index 18f5d3732d..75e57dcdb8 100644 --- a/src/courseware/course/sidebar/sidebars/course-outline/index.js +++ b/src/courseware/course/sidebar/sidebars/course-outline/index.js @@ -1,2 +1,12 @@ export { default as Sidebar } from './CourseOutlineTray'; export { default as Trigger, ID, LAYOUT } from './CourseOutlineTrigger'; + +export { + OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID, + OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID, + OUTLINE_SIDEBAR_SESSION_STORAGE_NAME, +} from './constants'; + +export { + checkIsOutlineSidebarAvailable, +} from './utils'; diff --git a/src/courseware/course/sidebar/sidebars/course-outline/utils.js b/src/courseware/course/sidebar/sidebars/course-outline/utils.js new file mode 100644 index 0000000000..c650a9a913 --- /dev/null +++ b/src/courseware/course/sidebar/sidebars/course-outline/utils.js @@ -0,0 +1,19 @@ +import { getConfig } from '@edx/frontend-platform'; + +import { + OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID, + OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID, +} from './constants'; + +// eslint-disable-next-line import/prefer-default-export +export const checkIsOutlineSidebarAvailable = () => { + const pluginSlots = getConfig().pluginSlots || {}; + const sidebarPluginSlots = [ + OUTLINE_SIDEBAR_DESKTOP_PLUGIN_SLOT_ID, + OUTLINE_SIDEBAR_MOBILE_PLUGIN_SLOT_ID, + ]; + + return sidebarPluginSlots.every( + key => Object.prototype.hasOwnProperty.call(pluginSlots, key) && pluginSlots[key].keepDefault === true, + ); +}; diff --git a/src/courseware/data/api.js b/src/courseware/data/api.js index cb557ae01e..17b11b6282 100644 --- a/src/courseware/data/api.js +++ b/src/courseware/data/api.js @@ -1,6 +1,7 @@ import { camelCaseObject, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth'; -import { appendBrowserTimezoneToUrl } from '../../utils'; + +import { appendBrowserTimezoneToUrl } from '@src/utils'; import { normalizeLearningSequencesData, normalizeMetadata, normalizeOutlineBlocks, normalizeSequenceMetadata, } from './utils'; @@ -99,17 +100,6 @@ export async function getCourseOutline(courseId) { return data.blocks ? normalizeOutlineBlocks(courseId, data.blocks) : null; } -/** - * Get waffle flag value that enable courseware outline sidebar. - * @param {string} courseId - The unique identifier for the course. - * @returns {Promise<{enabled: boolean}>} - The boolean value of enabling of the outline sidebar. - */ -export async function getCoursewareOutlineSidebarEnabledFlag(courseId) { - const url = new URL(`${getConfig().LMS_BASE_URL}/courses/${courseId}/courseware-sidebar/enabled/`); - const { data } = await getAuthenticatedHttpClient().get(url.href); - return { enabled: data.enabled || false }; -} - /** * Get the waffle flag value that default opens the courseware discussion sidebar. * @param {string} courseId - The unique identifier for the course. diff --git a/src/courseware/data/redux.test.js b/src/courseware/data/redux.test.js index 516832960c..d105b4346c 100644 --- a/src/courseware/data/redux.test.js +++ b/src/courseware/data/redux.test.js @@ -44,7 +44,6 @@ describe('Data layer integration tests', () => { const sequenceUrl = `${sequenceBaseUrl}/${sequenceMetadata.item_id}`; const sequenceId = sequenceBlocks[0].id; const unitId = unitBlocks[0].id; - const outlineSidebarSettingsUrl = `${getConfig().LMS_BASE_URL}/courses/${courseId}/courseware-sidebar/enabled/`; const rightSidebarSettingsUrl = `${getConfig().LMS_BASE_URL}/courses/${courseId}/show-default-right-sidebar/enabled/`; let store; @@ -60,7 +59,6 @@ describe('Data layer integration tests', () => { it('Should fail to fetch course and blocks if request error happens', async () => { axiosMock.onGet(courseUrl).networkError(); axiosMock.onGet(learningSequencesUrlRegExp).networkError(); - axiosMock.onGet(outlineSidebarSettingsUrl).networkError(); axiosMock.onGet(rightSidebarSettingsUrl).networkError(); await executeThunk(thunks.fetchCourse(courseId), store.dispatch); @@ -70,7 +68,6 @@ describe('Data layer integration tests', () => { courseId, courseOutline: {}, courseStatus: FAILED, - courseOutlineSidebarSettings: {}, rightSidebarSettings: {}, courseOutlineStatus: LOADING, sequenceId: null, @@ -113,7 +110,6 @@ describe('Data layer integration tests', () => { axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata); axiosMock.onGet(courseUrl).reply(200, courseMetadata); axiosMock.onGet(learningSequencesUrlRegExp).reply(200, buildOutlineFromBlocks(courseBlocks)); - axiosMock.onGet(outlineSidebarSettingsUrl).reply(200, { enabled: true }); axiosMock.onGet(rightSidebarSettingsUrl).reply(200, { enabled: true }); await executeThunk(thunks.fetchCourse(courseId), store.dispatch); @@ -124,7 +120,6 @@ describe('Data layer integration tests', () => { expect(state.courseware.courseId).toEqual(courseId); expect(state.courseware.sequenceStatus).toEqual('loading'); expect(state.courseware.sequenceId).toEqual(null); - expect(state.courseware.courseOutlineSidebarSettings).toEqual({ enabled: true }); expect(state.courseware.rightSidebarSettings).toEqual({ enabled: true }); // check that at least one key camel cased, thus course data normalized @@ -137,7 +132,6 @@ describe('Data layer integration tests', () => { axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata); axiosMock.onGet(courseUrl).reply(200, courseMetadata); axiosMock.onGet(learningSequencesUrlRegExp).reply(200, simpleOutline); - axiosMock.onGet(outlineSidebarSettingsUrl).reply(200, { enabled: false }); axiosMock.onGet(rightSidebarSettingsUrl).reply(200, { enabled: false }); await executeThunk(thunks.fetchCourse(courseId), store.dispatch); @@ -148,7 +142,6 @@ describe('Data layer integration tests', () => { expect(state.courseware.courseId).toEqual(courseId); expect(state.courseware.sequenceStatus).toEqual('loading'); expect(state.courseware.sequenceId).toEqual(null); - expect(state.courseware.courseOutlineSidebarSettings).toEqual({ enabled: false }); expect(state.courseware.rightSidebarSettings).toEqual({ enabled: false }); // check that at least one key camel cased, thus course data normalized diff --git a/src/courseware/data/selectors.js b/src/courseware/data/selectors.js index 55a5f5b765..a297945092 100644 --- a/src/courseware/data/selectors.js +++ b/src/courseware/data/selectors.js @@ -16,8 +16,6 @@ export const getCourseOutline = state => state.courseware.courseOutline; export const getCourseOutlineStatus = state => state.courseware.courseOutlineStatus; -export const getCoursewareOutlineSidebarSettings = state => state.courseware.courseOutlineSidebarSettings; - export const getRightSidebarSettings = state => state.courseware.rightSidebarSettings; export const getCourseOutlineShouldUpdate = state => state.courseware.courseOutlineShouldUpdate; diff --git a/src/courseware/data/slice.js b/src/courseware/data/slice.js index 5403e1a123..e52e9acde6 100644 --- a/src/courseware/data/slice.js +++ b/src/courseware/data/slice.js @@ -15,7 +15,6 @@ const slice = createSlice({ sequenceMightBeUnit: false, sequenceStatus: LOADING, courseOutline: {}, - courseOutlineSidebarSettings: {}, rightSidebarSettings: {}, courseOutlineStatus: LOADING, courseOutlineShouldUpdate: false, @@ -65,9 +64,6 @@ const slice = createSlice({ state.courseOutline = {}; state.courseOutlineStatus = FAILED; }, - setCoursewareOutlineSidebarSettings: (state, { payload }) => { - state.courseOutlineSidebarSettings = payload; - }, setRightSidebarSettings: (state, { payload }) => { state.rightSidebarSettings = payload; }, @@ -125,7 +121,6 @@ export const { fetchCourseOutlineRequest, fetchCourseOutlineSuccess, fetchCourseOutlineFailure, - setCoursewareOutlineSidebarSettings, setRightSidebarSettings, updateCourseOutlineCompletion, } = slice.actions; diff --git a/src/courseware/data/thunks.js b/src/courseware/data/thunks.js index 328eeffea3..4cd88d2624 100644 --- a/src/courseware/data/thunks.js +++ b/src/courseware/data/thunks.js @@ -1,15 +1,14 @@ import { logError, logInfo } from '@edx/frontend-platform/logging'; -import { getCourseHomeCourseMetadata } from '../../course-home/data/api'; +import { getCourseHomeCourseMetadata } from '@src/course-home/data/api'; import { addModel, addModelsMap, updateModel, updateModels, updateModelsMap, -} from '../../generic/model-store'; +} from '@src/generic/model-store'; import { getBlockCompletion, getCourseDiscussionConfig, getCourseMetadata, getCourseOutline, getCourseTopics, - getCoursewareOutlineSidebarEnabledFlag, getRightSidebarDefaultOpeningFlag, getLearningSequencesOutline, getSequenceMetadata, @@ -27,7 +26,6 @@ import { fetchCourseOutlineRequest, fetchCourseOutlineSuccess, fetchCourseOutlineFailure, - setCoursewareOutlineSidebarSettings, setRightSidebarSettings, updateCourseOutlineCompletion, } from './slice'; @@ -39,18 +37,15 @@ export function fetchCourse(courseId) { getCourseMetadata(courseId), getLearningSequencesOutline(courseId), getCourseHomeCourseMetadata(courseId, 'courseware'), - getCoursewareOutlineSidebarEnabledFlag(courseId), getRightSidebarDefaultOpeningFlag(courseId), ]).then(([ courseMetadataResult, learningSequencesOutlineResult, courseHomeMetadataResult, - courseOutlineSidebarDisableFlagResult, rightSidebarDefaultOpenFlagResult]) => { const fetchedMetadata = courseMetadataResult.status === 'fulfilled'; const fetchedCourseHomeMetadata = courseHomeMetadataResult.status === 'fulfilled'; const fetchedOutline = learningSequencesOutlineResult.status === 'fulfilled'; - const fetchedOutlineSidebarEnableFlag = courseOutlineSidebarDisableFlagResult.status === 'fulfilled'; const fetchedRightSidebarDefaultOpenFlag = rightSidebarDefaultOpenFlagResult.status === 'fulfilled'; if (fetchedMetadata) { @@ -91,12 +86,6 @@ export function fetchCourse(courseId) { })); } - if (fetchedOutlineSidebarEnableFlag) { - dispatch(setCoursewareOutlineSidebarSettings({ - enabled: courseOutlineSidebarDisableFlagResult.value.enabled, - })); - } - if (fetchedRightSidebarDefaultOpenFlag) { dispatch(setRightSidebarSettings({ enabled: rightSidebarDefaultOpenFlagResult.value.enabled, @@ -121,9 +110,6 @@ export function fetchCourse(courseId) { if (!fetchedCourseHomeMetadata) { logError(courseHomeMetadataResult.reason); } - if (!fetchedOutlineSidebarEnableFlag) { - logError(courseOutlineSidebarDisableFlagResult.reason); - } if (fetchedMetadata && fetchedCourseHomeMetadata) { if (courseHomeMetadataResult.value.courseAccess.hasAccess && fetchedOutline) { // User has access diff --git a/src/setupTest.js b/src/setupTest.js index 8a628da57d..0d35ceead5 100755 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -162,20 +162,17 @@ export async function initializeTestStore(options = {}, overrideStore = true) { const learningSequencesUrlRegExp = new RegExp(`${getConfig().LMS_BASE_URL}/api/learning_sequences/v1/course_outline/*`); let courseHomeMetadataUrl = `${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseMetadata.id}`; const discussionConfigUrl = new RegExp(`${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/*`); - const outlineSidebarSettingsUrl = `${getConfig().LMS_BASE_URL}/courses/${courseMetadata.id}/courseware-sidebar/enabled/`; const rightSidebarSettingsUrl = `${getConfig().LMS_BASE_URL}/courses/${courseMetadata.id}/show-default-right-sidebar/enabled/`; const outlineSidebarUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/navigation/${courseMetadata.id}`; courseHomeMetadataUrl = appendBrowserTimezoneToUrl(courseHomeMetadataUrl); const provider = options?.provider || 'legacy'; - const outlineSidebarSettings = options.outlineSidebarSettings || { enabled: true }; const rightSidebarSettings = options.rightSidebarSettings || { enabled: true }; axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata); axiosMock.onGet(courseHomeMetadataUrl).reply(200, courseHomeMetadata); axiosMock.onGet(learningSequencesUrlRegExp).reply(200, buildOutlineFromBlocks(courseBlocks)); axiosMock.onGet(discussionConfigUrl).reply(200, { provider }); - axiosMock.onGet(outlineSidebarSettingsUrl).reply(200, outlineSidebarSettings); axiosMock.onGet(rightSidebarSettingsUrl).reply(200, rightSidebarSettings); axiosMock.onGet(outlineSidebarUrl).reply(200, {