From dd6a499cfcc3db6594af97e296fe8f8f0f532d71 Mon Sep 17 00:00:00 2001
From: connorhaugh <49422820+connorhaugh@users.noreply.github.com>
Date: Mon, 4 Oct 2021 08:58:37 -0400
Subject: [PATCH] Feat lazy load jump nav destinations (#663)
In order to finish off TNL-7107 I needed to meet the acceptance criteria: When learners or educators select a section dropdown item they are taken to the first subsection within that section that is not completed by default. If all subsections are completed they should be taken to the first(subsection) in that section.
This reimagining of Jumpnav does that by lazy loading in the menuItem's destinations and routing the user using React-Router.
---
src/courseware/course/Course.jsx | 1 +
src/courseware/course/CourseBreadcrumbs.jsx | 108 +++++++++---------
.../course/CourseBreadcrumbs.test.jsx | 26 +++--
src/courseware/course/JumpNavMenuItem.jsx | 94 +++++++++++++++
.../course/JumpNavMenuItem.test.jsx | 78 +++++++++++++
src/courseware/data/thunks.js | 4 +-
6 files changed, 247 insertions(+), 64 deletions(-)
create mode 100644 src/courseware/course/JumpNavMenuItem.jsx
create mode 100644 src/courseware/course/JumpNavMenuItem.test.jsx
diff --git a/src/courseware/course/Course.jsx b/src/courseware/course/Course.jsx
index 748d8a40..7458fe7a 100644
--- a/src/courseware/course/Course.jsx
+++ b/src/courseware/course/Course.jsx
@@ -90,6 +90,7 @@ function Course({
courseId={courseId}
sectionId={section ? section.id : null}
sequenceId={sequenceId}
+ unitId={unitId}
//* * [MM-P2P] Experiment */
mmp2p={MMP2P}
/>
diff --git a/src/courseware/course/CourseBreadcrumbs.jsx b/src/courseware/course/CourseBreadcrumbs.jsx
index 14d76d31..013941fb 100644
--- a/src/courseware/course/CourseBreadcrumbs.jsx
+++ b/src/courseware/course/CourseBreadcrumbs.jsx
@@ -5,32 +5,18 @@ import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHome } from '@fortawesome/free-solid-svg-icons';
import { useSelector } from 'react-redux';
-import { Hyperlink, MenuItem, SelectMenu } from '@edx/paragon';
+import { SelectMenu } from '@edx/paragon';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
-import {
- sendTrackingLogEvent,
- sendTrackEvent,
-} from '@edx/frontend-platform/analytics';
import { useModel, useModels } from '../../generic/model-store';
/** [MM-P2P] Experiment */
import { MMP2PFlyoverTrigger } from '../../experiments/mm-p2p';
+import ConnectedJumpNavMenuItem from './JumpNavMenuItem';
function CourseBreadcrumb({
- content, withSeparator,
+ content, withSeparator, courseId, unitId,
}) {
- const defaultContent = content.filter(destination => destination.default)[0];
- const administrator = getAuthenticatedUser() ? getAuthenticatedUser().administrator : false;
- function logEvent(target) {
- const eventName = 'edx.ui.lms.jump_nav.selected';
- const payload = {
- target_name: target.label,
- id: target.id,
- current_id: defaultContent.id,
- widget_placement: 'breadcrumb',
- };
- sendTrackEvent(eventName, payload);
- sendTrackingLogEvent(eventName, payload);
- }
+ const defaultContent = content.filter(destination => destination.default)[0] || { id: courseId, label: '' };
+ const { administrator } = getAuthenticatedUser();
return (
<>
@@ -46,20 +32,20 @@ function CourseBreadcrumb({
>
{ getConfig().ENABLE_JUMPNAV !== 'true' || content.length < 2 || !administrator
? (
- {defaultContent.label}
+
+ {defaultContent.label}
)
: (
{content.map(item => (
-
+
))}
)}
@@ -72,58 +58,71 @@ CourseBreadcrumb.propTypes = {
content: PropTypes.arrayOf(
PropTypes.shape({
default: PropTypes.bool,
- url: PropTypes.string,
id: PropTypes.string,
label: PropTypes.string,
}),
).isRequired,
+ unitId: PropTypes.string,
withSeparator: PropTypes.bool,
+ courseId: PropTypes.string,
};
CourseBreadcrumb.defaultProps = {
withSeparator: false,
+ unitId: null,
+ courseId: null,
};
export default function CourseBreadcrumbs({
courseId,
sectionId,
sequenceId,
+ unitId,
/** [MM-P2P] Experiment */
mmp2p,
}) {
const course = useModel('coursewareMeta', courseId);
const courseStatus = useSelector(state => state.courseware.courseStatus);
- const sections = course ? Object.fromEntries(useModels('sections', course.sectionIds).map(section => [section.id, section])) : null;
- const possibleSequences = sections && sectionId ? sections[sectionId].sequenceIds : [];
- const sequences = Object.fromEntries(useModels('sequences', possibleSequences).map(sequence => [sequence.id, sequence]));
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
+ const allSequencesInSections = Object.fromEntries(useModels('sections', course.sectionIds).map(section => [section.id, {
+ default: section.id === sectionId,
+ title: section.title,
+ sequences: useModels('sequences', section.sequenceIds),
+ }]));
+
const links = useMemo(() => {
- const temp = [];
+ const chapters = [];
+ const sequentials = [];
if (courseStatus === 'loaded' && sequenceStatus === 'loaded') {
- temp.push(course.sectionIds.map(id => ({
- id,
- label: sections[id].title,
- default: (id === sectionId),
- // navigate to first sequence in section, (TODO: navigate to first incomplete sequence in section)
- url: `${getConfig().BASE_URL}/course/${courseId}/${sections[id].sequenceIds[0]}`,
- })));
- temp.push(sections[sectionId].sequenceIds.map(id => ({
- id,
- label: sequences[id].title,
- default: id === sequenceId,
- // first unit it section (TODO: navigate to first incomplete in sequence)
- url: `${getConfig().BASE_URL}/course/${courseId}/${sequences[id].id}/${sequences[id].unitIds[0]}`,
- })));
+ Object.entries(allSequencesInSections).forEach(([id, section]) => {
+ chapters.push({
+ id,
+ label: section.title,
+ default: section.default,
+ sequences: section.sequences,
+ });
+ if (section.default) {
+ section.sequences.forEach(sequence => {
+ sequentials.push({
+ id: sequence.id,
+ label: sequence.title,
+ default: sequence.id === sequenceId,
+ sequences: [sequence],
+ });
+ });
+ }
+ });
}
- return temp;
- }, [courseStatus, sections, sequences]);
+ return [chapters, sequentials];
+ }, [courseStatus, sequenceStatus, allSequencesInSections]);
+
return (