Allow loading with no unit, and of sequences with no unitIds. (#34)

This requires some defensive programming here and there to let things load in a minimal state.
This commit is contained in:
David Joy
2020-03-23 16:40:50 -04:00
committed by GitHub
parent 781508dd03
commit c8be4c401f
4 changed files with 37 additions and 21 deletions

View File

@@ -55,7 +55,7 @@ function Sequence({
const logEvent = (eventName, widgetPlacement, targetUnitId) => {
// Note: tabs are tracked with a 1-indexed position
// as opposed to a 0-index used throughout this MFE
const currentIndex = sequence.unitIds.indexOf(unitId);
const currentIndex = sequence.unitIds.length > 0 ? sequence.unitIds.indexOf(unitId) : 0;
const payload = {
current_tab: currentIndex + 1,
id: unitId,
@@ -110,7 +110,7 @@ function Sequence({
const gated = sequence.gatedContent !== undefined && sequence.gatedContent.gated;
if (sequenceStatus === 'loaded' && unit) {
if (sequenceStatus === 'loaded') {
return (
<div className="sequence">
<SequenceNavigation
@@ -147,7 +147,7 @@ function Sequence({
/>
</Suspense>
)}
{!gated && (
{!gated && unitId !== null && (
<Unit
key={unitId}
id={unitId}

View File

@@ -6,6 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import UnitButton from './UnitButton';
import SequenceNavigationTabs from './SequenceNavigationTabs';
import { useSequenceNavigationMetadata } from './hooks';
@@ -22,8 +23,30 @@ export default function SequenceNavigation({
const sequence = useModel('sequences', sequenceId);
const { isFirstUnit, isLastUnit } = useSequenceNavigationMetadata(sequenceId, unitId);
const isLocked = sequence.gatedContent !== undefined && sequence.gatedContent.gated;
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
return (
const renderUnitButtons = () => {
if (isLocked) {
return (
<UnitButton unitId={unitId} title="" contentType="lock" isActive onClick={() => {}} />
);
}
if (sequence.unitIds.length === 0 || unitId === null) {
return (
<div style={{ flexBasis: '100%', minWidth: 0, borderBottom: 'solid 1px #EAEAEA' }} />
);
}
return (
<SequenceNavigationTabs
unitIds={sequence.unitIds}
unitId={unitId}
showCompletion={sequence.showCompletion}
onNavigate={onNavigate}
/>
);
};
return sequenceStatus === 'loaded' && (
<nav className={classNames('sequence-navigation', className)}>
<Button className="previous-btn" onClick={previousSequenceHandler} disabled={isFirstUnit}>
<FontAwesomeIcon icon={faChevronLeft} className="mr-2" size="sm" />
@@ -33,16 +56,7 @@ export default function SequenceNavigation({
description="The Previous button in the sequence nav"
/>
</Button>
{isLocked ? <UnitButton unitId={unitId} title="" contentType="lock" isActive onClick={() => {}} /> : (
<SequenceNavigationTabs
unitIds={sequence.unitIds}
unitId={unitId}
showCompletion={sequence.showCompletion}
onNavigate={onNavigate}
/>
)}
{renderUnitButtons()}
<Button className="next-btn" onClick={nextSequenceHandler} disabled={isLastUnit}>
<FormattedMessage
defaultMessage="Next"
@@ -56,8 +70,8 @@ export default function SequenceNavigation({
}
SequenceNavigation.propTypes = {
unitId: PropTypes.string.isRequired,
sequenceId: PropTypes.string.isRequired,
unitId: PropTypes.string,
className: PropTypes.string,
onNavigate: PropTypes.func.isRequired,
nextSequenceHandler: PropTypes.func.isRequired,
@@ -66,4 +80,5 @@ SequenceNavigation.propTypes = {
SequenceNavigation.defaultProps = {
className: null,
unitId: null,
};

View File

@@ -41,14 +41,14 @@ function normalizeBlocks(courseUsageKey, blocks) {
models.courses[block.id] = {
id: courseUsageKey,
title: block.display_name,
sectionIds: block.children,
sectionIds: block.children || [],
};
break;
case 'chapter':
models.sections[block.id] = {
id: block.id,
title: block.display_name,
sequenceIds: block.children,
sequenceIds: block.children || [],
};
break;
@@ -57,7 +57,7 @@ function normalizeBlocks(courseUsageKey, blocks) {
id: block.id,
title: block.display_name,
lmsWebUrl: block.lms_web_url,
unitIds: block.children,
unitIds: block.children || [],
};
break;
case 'vertical':

View File

@@ -5,7 +5,7 @@ import {
getSequenceMetadata,
} from './api';
import {
addModelsMap, updateModel, updateModels,
addModelsMap, updateModel, updateModels, updateModelsMap,
} from '../model-store';
import {
fetchCourseRequest,
@@ -40,11 +40,12 @@ export function fetchCourse(courseUsageKey) {
modelType: 'sections',
modelsMap: sections,
}));
dispatch(addModelsMap({
// We update for sequences and units because the sequence metadata may have come back first.
dispatch(updateModelsMap({
modelType: 'sequences',
modelsMap: sequences,
}));
dispatch(addModelsMap({
dispatch(updateModelsMap({
modelType: 'units',
modelsMap: units,
}));